Compare commits

..

142 Commits

Author SHA1 Message Date
Jørgen Henrichsen b042c3ee6c Bump version 0.11.2. 2019-11-28 18:49:18 +01:00
Jørgen Henrichsen ee5db0c0d5 Merge pull request #87 from biesbjerg/always-emit-ready-event
Fixes a problem where the `ready` event would not be fired when passing `playWhenReady: true`.
2019-11-28 18:47:11 +01:00
Kim Biesbjerg a9509d454f always emit ready event 2019-11-01 10:41:37 +01:00
Jørgen Henrichsen e888c7954a Update Readme. Update podspec.
Bump version to 0.11.1.
2019-08-22 22:55:32 +02:00
Jørgen Henrichsen eea9aee4ec Merge pull request #74 from cbess/patch-1
Seek to initial time
2019-08-22 22:52:36 +02:00
Jørgen Henrichsen f2c95fa48c Merge branch 'master' into patch-1 2019-08-22 22:39:46 +02:00
Jørgen Henrichsen 5c8ac4da6b Update README
Remove incorrect recommendation to enable Remote Notifications.
2019-08-22 21:27:36 +02:00
C. Bess 5369d4f4e8 Seek to initial time
Use initial time operation, even for play when ready
2019-08-12 23:13:36 -05:00
Jørgen Henrichsen 09a7548f3a Update README. Update podspec.
Bump verision to 0.11.0.
2019-07-12 18:13:46 +02:00
Jørgen Henrichsen 565b9a04d4 Merge pull request #71 from dcvz/feature/asset-options
Add ability to set asset init options
2019-07-12 18:12:23 +02:00
David Chavez c2ec751ec0 Address review feedback 2019-07-10 13:56:05 +02:00
David Chavez 2c36c6c239 Add ability to set asset init options 2019-07-06 12:04:49 +02:00
Jørgen Henrichsen 1c4cbf676d Merge pull request #70 from jorgenhenrichsen/more-detaile-player-states
More detailed player states
2019-07-03 18:29:43 +02:00
Jørgen Henrichsen ec9492da17 Add tests for the loading state. 2019-07-03 17:48:28 +02:00
Jørgen Henrichsen 5add7699ff Added an example of how to handle network error.
The example app will now handle a network error, and reload when play is called, after a item failed to load because of a network error.
2019-07-03 16:41:14 +02:00
Jørgen Henrichsen bc1d775875 Reset audio file to spotify preview in example app. 2019-07-03 15:06:50 +02:00
Jørgen Henrichsen 4c1a545e87 Update podspec. Remove swift version file.
Clean up the podspec file and set swift version to 5.0. Remove the .swift-version file as it's use is deprecated.
2019-07-03 14:53:24 +02:00
Jørgen Henrichsen f31b52f81b Make the ready state reachable for items with initial time.
Fixes a problem where the .ready state would not be reached for audio items that had an initial time.
2019-07-03 14:25:43 +02:00
Jørgen Henrichsen 355c729078 Update README. Update podspec.
Bump version to 0.10.0.
2019-07-03 13:22:27 +02:00
Jørgen Henrichsen 719d3c852b Add an activityindicator to the example.
Shows how an activityindicator can be used to indicate loading, when player is loading or buffering.
2019-07-03 13:21:41 +02:00
Jørgen Henrichsen 4f33d7e688 Add a new state buffering.
Introduces an additional state to better describe the current state of the player.
Previously the player would be `idle` until the  item was loaded, then changing to `ready`. When the player started to play it would first enter `loading`, then subsequently `playing`.
Now it will instead immediately enter `loading`, then when the item is loaded it will enter `ready`. When the item then is played it will enter `buffering` and then `playing`.
This should make it easier to update UI accordingly to the players current state.
2019-07-03 11:23:10 +02:00
Jørgen Henrichsen 56d0633df0 Update README. Update podspec.
Bump to version 0.9.3.
2019-07-01 16:23:01 +02:00
Jørgen Henrichsen 825e508ecb Merge pull request #67 from jorgenhenrichsen/async-fixes
Async fixes
2019-07-01 16:14:20 +02:00
Jørgen Henrichsen 76d9dc17af Merge branch 'master' into async-fixes 2019-07-01 15:38:45 +02:00
Jørgen Henrichsen 8ceb0216a0 Merge pull request #65 from dcvz/feature/feedback-commands
Add commands for like, dislike and bookmark
2019-07-01 15:38:33 +02:00
David Chavez db41634631 Fix some missing sets 2019-06-25 10:49:10 +02:00
David Chavez 15843b5fe8 Add commands for like, dislike and bookmark 2019-06-24 23:57:17 +02:00
Jørgen Henrichsen 4241b484b2 Removed commented prints. 2019-06-23 18:19:08 +02:00
Jørgen Henrichsen 92bf0b96b6 Remove cancelloading in the load method.
This was already done in the reset(soft:) method.
Removed the nil check before canceling the pendingAsset load, since the coalescing `?` operator was already used anyway.
2019-06-23 18:13:48 +02:00
Jørgen Henrichsen 457dff7c01 Use weak capturing of the AudioPlayer. Remove initial time test.
Weak capturing of the AudioPlayer is done in the listener callback functions in order to not receive INVOPs in the event the audioPlayer is deallocated.
The initial time test is removed because of the unreliable results it produces.
2019-06-23 18:06:43 +02:00
Jørgen Henrichsen c90e9c4976 Unregister observers in their deinit method. 2019-06-23 16:41:47 +02:00
Jørgen Henrichsen 649bb01e89 Weak self and cancel previous load in load(from:playWhenReady).
Use weak self in the loadValuesAsync callback in the event where this is called after the player has be deintialized.
Cancel loading the pendingAsset before loading a new one.
2019-06-23 16:35:50 +02:00
Jørgen Henrichsen 965f7dbbe1 Update README and podspec.
Bump version to 0.9.2.
2019-06-19 12:42:31 +02:00
Jørgen Henrichsen 536414bd44 Merge pull request #51 from minhtc/async-load-asset
load asset async to prevent freeze UI when streaming audio from internet
2019-06-19 12:39:00 +02:00
HackerMeo b7d5db0a55 Merge pull request #1 from jorgenhenrichsen/minhtc-async-load-asset
Fix test problems and a few other things
2019-06-19 16:07:40 +07:00
Jørgen Henrichsen d0907b16c8 Merge branch 'async-load-asset' into minhtc-async-load-asset 2019-06-18 21:34:24 +02:00
Jørgen Henrichsen 60eb4b4676 Removed the currentTime test.
Removes the test that always fails in the CI env at bitrise.
2019-06-18 11:30:47 +02:00
Jørgen Henrichsen 4ec9e9c2a2 Update currentTime tests.
Use a shorter `timeEventFrequenct` in order to recieve the callback quicker, so the test can pass quicker.
2019-06-18 11:16:04 +02:00
Jørgen Henrichsen b2efdf4d65 Rewrote the current time test.
Checks each time a second elapses, instead of just at state == `playing`.
2019-06-13 22:33:41 +02:00
Jørgen Henrichsen d9badfec9b Do not set automaticallyWaitsToMinimizeStalling to false when an item is loaded. 2019-06-13 21:42:47 +02:00
Jørgen Henrichsen 4d0f29adf6 Increased timeout for expectations. 2019-06-13 21:42:47 +02:00
Jørgen Henrichsen f817bc5840 Wrote AudioPlayerTests using the XCTest framework. 2019-06-13 21:42:47 +02:00
Jørgen Henrichsen 5755b70e1b Rewrote AVPlayerWrapperTests using the XCTest framework. 2019-06-13 21:42:47 +02:00
Jørgen Henrichsen 83ac2af5d6 Set timepitchingalgo on audio ready. Update tests to support async loading. 2019-06-13 21:10:57 +02:00
minhtcx a2f001a968 load asset async to prevent freeze UI when streaming audio from internet 2019-06-13 21:10:56 +02:00
Jørgen Henrichsen 9390064581 Merge pull request #62 from anharismail/master
Logo SwiftAudio
2019-06-13 12:15:45 +02:00
Anhar Ismail 7a45ee9e47 Update README.md 2019-06-13 16:57:58 +07:00
Anhar Ismail 17a4b18333 Add files via upload 2019-06-13 16:56:34 +07:00
Anhar Ismail ad01101d3c Create logomark.png 2019-06-13 16:55:40 +07:00
Jørgen Henrichsen d1bbc94bdd Update Readme. Update podspec.
Bump version to 0.9.1.
2019-06-11 13:48:16 +02:00
Jørgen Henrichsen fb5a8dde6c Merge pull request #61 from minhtc/fix-exclusive-access
Fix crash on modification requires exclusive access
2019-06-11 13:47:03 +02:00
minhtcx 9b71040f43 fix crash on modification requires exclusive access 2019-06-10 12:02:46 +07:00
Jørgen Henrichsen 5e50ea48d6 Merge pull request #60 from jorgenhenrichsen/recover-from-AVPlayer-failed
Recover from AVPlayer failed
2019-05-11 19:41:12 +02:00
Jørgen Henrichsen 92a804e9e4 Update Readme. Update podspec.
Bump version to v0.9.0.
2019-05-11 19:30:59 +02:00
Jørgen Henrichsen af4635d047 Remove AVPlayer parameter from init in both AudioPlayer and AVPlayerWrapper. 2019-05-11 19:29:36 +02:00
Jørgen Henrichsen addebef7f9 Add more detailed description on the fail event. 2019-05-11 18:14:54 +02:00
Jørgen Henrichsen 46022ef0d6 Add event when AVPlayer is recreated.
The AudioSession category will need to be set again when this event is emitted.
2019-05-11 17:56:43 +02:00
Jørgen Henrichsen 386dc5202c Recreate the AVPlayer when loading an item, if the previous item failed. 2019-05-11 17:46:55 +02:00
Jørgen Henrichsen 17166093c2 Correctly set isObserving when stopping the observation. 2019-05-11 17:46:27 +02:00
Jørgen Henrichsen c06b8ce64c Made the player observer not retain the AVPlayer.
They will also stop observing the current AVPlayer if a new one is going to be set.
2019-05-11 17:34:24 +02:00
Minh Tran aab8a2302b Merge branch 'master' into async-load-asset 2019-04-19 12:55:36 +07:00
Jørgen Henrichsen 5c8c83f914 Merge pull request #54 from jorgenhenrichsen/swift5
Swift 5
2019-04-18 17:18:00 +02:00
Jørgen Henrichsen ef2d9f5a90 Update README. Update podspec.
Bump version to 0.8.0.
2019-04-18 17:06:57 +02:00
Jørgen Henrichsen 08ebb473f2 Add a custom case to the TimeEventFrequency enum.
Will fix #52.
2019-04-18 16:53:07 +02:00
Jørgen Henrichsen d32a041159 Updated to recommended project settings. 2019-04-18 16:53:07 +02:00
Jørgen Henrichsen 1057a7fca6 Add new handler for non-frozen enums. 2019-04-18 16:53:07 +02:00
Jørgen Henrichsen cee2be50e2 Removed the AudioPlayerDelegate.
Events must be used instead.
2019-04-18 16:53:07 +02:00
Jørgen Henrichsen 8161c3cf02 Update to Swift 5. 2019-04-18 16:53:07 +02:00
Jørgen Henrichsen bb0f301383 Update dependencies. 2019-04-18 16:53:07 +02:00
Jørgen Henrichsen 9da1347d12 Update issue templates
Added issue templates based on Githubs default templates
2019-04-18 12:45:07 +02:00
minhtcx 4a512c6aa0 load asset async to prevent freeze UI when streaming audio from internet 2019-04-17 17:01:55 +07:00
Jørgen Henrichsen f8026d0915 Update podspec. Update README.
Bump versions to 0.7.2.
2019-04-16 09:15:15 +02:00
Jørgen Henrichsen 9d6534f2c9 Merge pull request #49 from dcvz/master
Add `buferredPosition` property
2019-04-16 09:14:10 +02:00
David Chavez c55c25686c Add buferredPosition property 2019-04-14 23:55:44 +02:00
Jørgen Henrichsen 852578c914 Merge pull request #47 from jorgenhenrichsen/queue-access
Queue access
2019-04-07 21:47:09 +02:00
Jørgen Henrichsen 0ae0427556 Update README. Update podspec.
Bump versions to 0.7.1.
2019-04-07 21:40:38 +02:00
Jørgen Henrichsen 9170be5e36 More information available in the QueuedAudioPlayer.
Can get the currentIndex and all items in the queue.
2019-04-07 20:58:27 +02:00
Jørgen Henrichsen 3930096357 Merge pull request #44 from jorgenhenrichsen/v0.7.0
V0.7.0
2019-03-10 12:22:24 +01:00
Jørgen Henrichsen 9e8ebdfca1 Update README. Update podspec.
Bump versions to 0.7.0.
2019-03-10 10:53:14 +01:00
Jørgen Henrichsen 31f4745bae Update README.
Updated description of events.
2019-03-10 10:47:46 +01:00
Jørgen Henrichsen 4a8af1bce3 Removed unneeded mark. 2019-03-10 10:39:32 +01:00
Jørgen Henrichsen 8e6b72613d Set volume of player in tests to 0. 2019-03-10 10:36:30 +01:00
Jørgen Henrichsen ae760c4cac Put events in seperate struct.
Makes it easier to discover which events are available.
2019-03-10 09:37:00 +01:00
Jørgen Henrichsen b3856e0a7a Tests for AudioPlayer events. 2019-03-10 01:02:24 +01:00
Jørgen Henrichsen 757e5f476c Use direct closure handlers for events. 2019-03-10 01:02:04 +01:00
Jørgen Henrichsen 1f5eae407d Make event calls async.
- Events will be emitted on the utility class.
- Use a semaphore to avoid race conditions in the Event class.
- Update example to dispatch ui updates to the main queue.
2019-03-09 21:30:50 +01:00
Jørgen Henrichsen 29660371c9 Updated tests to use events. 2019-03-09 21:30:02 +01:00
Jørgen Henrichsen c2530a0193 Update README.
Add details about the event handlers.
2019-03-09 20:31:27 +01:00
Jørgen Henrichsen 6804cf5163 Add events as an alternative to delegate. Updated example app.
This allows for selective listening to events, and no need to implement empty uneeded delegate methods.
Added a deprecation message to the delegate class, will most likely remove AudioPlayerDelegate in a future update.
2019-03-09 20:18:07 +01:00
Jørgen Henrichsen 6ea2e082b0 Call func audioPlayer(itemPlaybackEndedWithReason:) directly, instead of calling the AVWrapperDelegate implementation method.
Also remove the corresponding delegate method in the AVWrapper, and add a `AVWrapperItemDidPlayToEndTime` method instead.
2019-03-03 20:04:27 +01:00
Jørgen Henrichsen b6b4599ef5 Update nowPlayingInfo elapsed time immediately when seeking.
Causes less jumpy slider when seeking from the lockscreen.
Will rewind to the currentTime if the seeking fails.
2019-03-03 19:46:10 +01:00
Jørgen Henrichsen 3ccf9ed20e Update README
Add a missing `shared` call for the AudioSessionController.
2019-03-03 16:26:36 +01:00
Jørgen Henrichsen d6eb187788 Update README
Add more descriptions on setting the nowPlayingInfo
2019-03-03 16:25:46 +01:00
Jørgen Henrichsen 1febb782d8 Add tests for NowPlayingInfoController. 2019-03-03 16:10:30 +01:00
Jørgen Henrichsen c4c6f42ac0 Added clear() method. Use NowPlayingInfoCenter protocol.
`NowPlayingInfoCenter` makes the info center mockable, so the controller can be easily tested.
2019-03-03 16:09:29 +01:00
Jørgen Henrichsen d8b4466629 Add test for NowPlayingInfo artwork. 2019-03-01 12:21:00 +01:00
Jørgen Henrichsen cb9dec49b2 Test updating of NowPlayingInfo.
* Create protocol NowPlayingInfoControllerProtocol to make it more testable (so it can be mocked).
2019-02-28 23:06:03 +01:00
Jørgen Henrichsen 7e46a91e73 Don't reset info dict on set(keyValues:). Actually apply new values to infocenter.
The set(keyValues:) function did not actually apply the new values to the nowPlayingInfo, but does now.
2019-02-27 22:35:14 +01:00
Jørgen Henrichsen 8ca4a873a5 Remove reloadNowPlayingInfo() and replace it with two seperate functions for meta values and playbackvalues.
- loadNowPlayingMetaValues() loads the meta data from the AudioItem into the NowPlayingInfoController.
- updateNowPlayingPlaybackValues() updates the playback related values in the NowPlayingInfoController.
- Earlier, reloadNowPlayingInfo would have no effect if `automaticallyUpdateNowPlayingInfo`  was set to false. This is no longer the case, and both playbackValues and meta data can be forced to update without the player loading it automatically.
- Playback values are no longer updated each second in the NowPlayingInfoController. It should be sufficient to keep the rate, currentTime and duration in sync when they change.
2019-02-27 17:03:17 +01:00
Jørgen Henrichsen 3f54de70bc Merge pull request #42 from jorgenhenrichsen/bitrise
Bitrise
2019-02-24 14:21:28 +01:00
Jørgen Henrichsen cae549a401 Merge branch 'bitrise' of github.com:jorgenhenrichsen/SwiftAudio into bitrise 2019-02-24 14:07:41 +01:00
Jørgen Henrichsen 85fecef45e Deleted travis.yml. 2019-02-24 14:06:38 +01:00
Jørgen Henrichsen e2375f7a82 Update README
Use Bitrise for build status
2019-02-24 13:49:39 +01:00
Jørgen Henrichsen 3c8402503b Update podspec.
Bump version to 0.6.2
2019-02-24 13:19:08 +01:00
Jørgen Henrichsen 4c46137498 Update README
Bump version to 0.6.2
2019-02-24 13:18:20 +01:00
Jørgen Henrichsen a4e023d75f Merge pull request #41 from jorgenhenrichsen/initial-time
Add possiblity to start AudioItem from certain time.
2019-02-24 13:17:26 +01:00
Jørgen Henrichsen fe549a522a Update README
Add info about `InitialTiming`
2019-02-24 12:30:43 +01:00
Jørgen Henrichsen 6cc0638a70 Add possiblity to start AudioItem from certain time. 2019-02-24 11:58:03 +01:00
Jørgen Henrichsen 34c1f493ae Update podspec. Update Readme.
Bump versions to 0.6.1.
2019-01-31 22:08:16 +01:00
Jørgen Henrichsen 30750a0c81 Merge pull request #40 from jorgenhenrichsen/fix/seekto-imprecision
Increase timescale in seekto(time:) method.
2019-01-31 21:59:45 +01:00
Jørgen Henrichsen e57cf9d2e5 Increase timescale in seekto(time:) method.
Stops truncating of the seeked to time.
Also updated seekTo test to actually test the seeked value.
2019-01-31 18:31:34 +01:00
Jørgen Henrichsen 0eeedc6467 Merge pull request #38 from dcvz/patch-1
Get duration directly from asset
2019-01-31 18:26:51 +01:00
David Chavez e96cbf6337 Get duration directly from asset
It is more reliable because the current item directly will return NaN depending on what's loaded.
2019-01-30 20:39:19 +01:00
Jørgen Henrichsen debc1c519f Merge pull request #36 from jorgenhenrichsen/swift4.2
- Update to Swift 4.2
- Use Xcode 10.1 in CI
- Fix some compiler warnings in the project
- Removes custom `AudioSessionCategory`-enum as `AudioSession.Category` is introduced with Swift 4.2
2018-12-25 18:11:20 +01:00
Jørgen Henrichsen 09bec023a9 Update project version to 0.6.0. 2018-12-25 18:01:40 +01:00
Jørgen Henrichsen 44e022389c Merge branch 'swift4.2' of github.com:jorgenhenrichsen/SwiftAudio into swift4.2 2018-12-25 17:39:52 +01:00
Jørgen Henrichsen 05ca97b8eb Deleted AudioSessionCategory file. 2018-12-25 17:39:32 +01:00
Jørgen Henrichsen f6ff2b4cc0 Update travis
Test on iOS 11.4
2018-12-25 11:39:45 +01:00
Jørgen Henrichsen 9e3f5c0291 Use longer source for duration testing. 2018-12-23 14:36:39 +01:00
Jørgen Henrichsen 8ba40ca45f Updated project settings. 2018-12-23 14:30:23 +01:00
Jørgen Henrichsen 9919bde9fe Fixed compiler warnings. 2018-12-23 14:28:44 +01:00
Jørgen Henrichsen 3ba62d8657 Update travis config
Update to Xcode 10.1 and iOS 12.1
2018-12-23 12:52:27 +01:00
Jørgen Henrichsen 0f283b171f Update swift-version 2018-12-23 12:43:48 +01:00
Jørgen Henrichsen f6b5e30e85 Converted SwiftAudio and example to Swift 4.2 2018-12-23 12:38:39 +01:00
Jørgen Henrichsen 04296fa681 Update podspec. Update readme.
Bumpe versions to 0.5.0.
2018-11-18 23:42:40 +01:00
Jørgen Henrichsen 252ed947d2 Merge pull request #34 from jorgenhenrichsen/optional-time-pitch-algo
Optional time pitch algo
2018-11-18 23:35:11 +01:00
Jørgen Henrichsen b5fdb5c54e Merge branch 'master' into optional-time-pitch-algo 2018-11-18 22:59:00 +01:00
Jørgen Henrichsen 1b4c0b0d3b Update Readme. Update podspec.
Verision 0.4.4
2018-11-16 11:54:49 +01:00
Jørgen Henrichsen e0aa2a09a9 Merge pull request #35 from Alex601t/issue26
Fixes #26
2018-11-16 11:52:44 +01:00
Alexander ab0eb4f8eb Merge pull request #1 from jorgenhenrichsen/Alex601t-issue26
Observe loadedTimeRanges for AVPlayerItem
Use items duration.seconds if available.
2018-11-16 10:58:49 +03:00
Jørgen Henrichsen 99e7c65bbc Use items duration.seconds if available. 2018-11-15 20:35:07 +01:00
Jørgen Henrichsen 9072259631 Observe loadedTimeRanges for AVPlayerItem 2018-11-14 16:38:55 +01:00
Alexander eb9af1007a Issue 26:
Handling player current item duration from loaded time ranges
2018-11-14 16:34:58 +03:00
Jørgen Henrichsen 69d3a9c0c0 Test TimePitchAlgorithms in the AudioPlayer. 2018-11-14 10:06:14 +01:00
Jørgen Henrichsen 43821c68a9 Made DefaultAudioItem not conform to TimePitching.
New DefaultAudioItemTimePitching can be used instead.
2018-11-14 10:05:20 +01:00
Jørgen Henrichsen 610ff4c7f3 Made getter for wrapper internal.
Makes testing easier.
2018-11-14 10:04:12 +01:00
Jørgen Henrichsen 1fc533214f Update Readme.
- Remove timePitch algorithm from DefaultAudioItem inits
- Add audioTimePitchAlgorithm in the list of configurations
2018-11-09 13:50:53 +01:00
Jørgen Henrichsen 71f22c3e25 Time pitch algorithm is no longer required from AudioItems.
New protocol TimePitcher can be conformed to if AudioItems need to give their own Time Pitch Algorithm.
2018-11-09 13:45:48 +01:00
Jørgen Henrichsen 8a5e6d18cc Update Readme
Bump carthage install version
2018-11-08 23:13:02 +01:00
Jørgen Henrichsen a7f53bfec9 Update Readme. Update podspec.
Bump versions to 0.4.3
2018-11-08 23:11:49 +01:00
Jørgen Henrichsen 7b10f0476b Merge pull request #33 from jorgenhenrichsen/unsubscribe-observers
Unregister observer before removing AVPlayerItem.
2018-11-08 23:09:07 +01:00
Jørgen Henrichsen 618da75339 Unregister observer before removing AVPlayerItem.
Should fix a problem where the AVPlayerWrapper's item was deinitialized while observers where observing the item.
2018-11-08 17:59:24 +01:00
124 changed files with 3217 additions and 2225 deletions
+32
View File
@@ -0,0 +1,32 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
If needed, include code examples for the problem or steps to reproduce.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Version [e.g. 0.7.0]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6, Simulator iPhone6 ...]
- OS: [e.g. iOS8.1]
- Version [e.g. 0.7.0]
**Additional context**
Add any other context about the problem here.
+20
View File
@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution or feature you would like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
-1
View File
@@ -1 +0,0 @@
4.0
-17
View File
@@ -1,17 +0,0 @@
# references:
# * http://www.objc.io/issue-6/travis-ci.html
# * https://github.com/supermarin/xcpretty#usage
osx_image: xcode9.4
language: swift
cache: cocoapods
podfile: Example/Podfile
before_install:
- gem install cocoapods # Since Travis is not always on latest version
- pod install --project-directory=Example --repo-update
script:
- set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace Example/SwiftAudio.xcworkspace -scheme SwiftAudio-Example -sdk iphonesimulator11.4 -destination "OS=11.4,name=iPhone X" | xcpretty
- pod lib lint
after_success:
- bash <(curl -s https://codecov.io/bash) -J 'SwiftAudio'
+2 -2
View File
@@ -7,8 +7,8 @@ target 'SwiftAudio_Example' do
target 'SwiftAudio_Tests' do
inherit! :search_paths
pod 'Quick', '~> 1.3.0'
pod 'Nimble' , '~> 7.3.0'
pod 'Quick', '~> 2.0.0'
pod 'Nimble' , '~> 8.0.0'
end
end
+9 -9
View File
@@ -1,11 +1,11 @@
PODS:
- Nimble (7.3.1)
- Quick (1.3.2)
- SwiftAudio (0.3.3)
- Nimble (8.0.1)
- Quick (2.0.0)
- SwiftAudio (0.7.2)
DEPENDENCIES:
- Nimble (~> 7.3.0)
- Quick (~> 1.3.0)
- Nimble (~> 8.0.0)
- Quick (~> 2.0.0)
- SwiftAudio (from `../`)
SPEC REPOS:
@@ -18,10 +18,10 @@ EXTERNAL SOURCES:
:path: "../"
SPEC CHECKSUMS:
Nimble: 04f732da099ea4d153122aec8c2a88fd0c7219ae
Quick: 2623cb30d7a7f41ca62f684f679586558f483d46
SwiftAudio: 2e712c3e04cf172d05639d7bb1516db7afd195da
Nimble: 45f786ae66faa9a709624227fae502db55a8bdd0
Quick: ce1276c7c27ba2da3cb2fd0cde053c3648b3b22d
SwiftAudio: a7bb22e98fd48fd908f7ffca992166e0057ce8ea
PODFILE CHECKSUM: 8a75946cbc65d8d98176f80a88d8363a28d118ce
PODFILE CHECKSUM: 08ea4075437f8ff3c4f84ed70827a8132461373f
COCOAPODS: 1.5.3
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "SwiftAudio",
"version": "0.3.3",
"version": "0.7.2",
"summary": "Easy audio streaming for iOS",
"description": "SwiftAudio is an audio player written in Swift, making it simpler to work with audio playback from streams and files.",
"homepage": "https://github.com/jorgenhenrichsen/SwiftAudio",
@@ -13,7 +13,7 @@
},
"source": {
"git": "https://github.com/jorgenhenrichsen/SwiftAudio.git",
"tag": "0.3.3"
"tag": "0.7.2"
},
"platforms": {
"ios": "10.0"
+9 -9
View File
@@ -1,11 +1,11 @@
PODS:
- Nimble (7.3.1)
- Quick (1.3.2)
- SwiftAudio (0.3.3)
- Nimble (8.0.1)
- Quick (2.0.0)
- SwiftAudio (0.7.2)
DEPENDENCIES:
- Nimble (~> 7.3.0)
- Quick (~> 1.3.0)
- Nimble (~> 8.0.0)
- Quick (~> 2.0.0)
- SwiftAudio (from `../`)
SPEC REPOS:
@@ -18,10 +18,10 @@ EXTERNAL SOURCES:
:path: "../"
SPEC CHECKSUMS:
Nimble: 04f732da099ea4d153122aec8c2a88fd0c7219ae
Quick: 2623cb30d7a7f41ca62f684f679586558f483d46
SwiftAudio: 2e712c3e04cf172d05639d7bb1516db7afd195da
Nimble: 45f786ae66faa9a709624227fae502db55a8bdd0
Quick: ce1276c7c27ba2da3cb2fd0cde053c3648b3b22d
SwiftAudio: a7bb22e98fd48fd908f7ffca992166e0057ce8ea
PODFILE CHECKSUM: 8a75946cbc65d8d98176f80a88d8363a28d118ce
PODFILE CHECKSUM: 08ea4075437f8ff3c4f84ed70827a8132461373f
COCOAPODS: 1.5.3
+7 -22
View File
@@ -4,6 +4,7 @@
[![CocoaPods](https://img.shields.io/cocoapods/v/Nimble.svg)](https://cocoapods.org/pods/Nimble)
[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![Platforms](https://img.shields.io/cocoapods/p/Nimble.svg)](https://cocoapods.org/pods/Nimble)
[![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com)
Use Nimble to express the expected outcomes of Swift
or Objective-C expressions. Inspired by
@@ -306,8 +307,7 @@ In Nimble, it's easy to make expectations on values that are updated
asynchronously. Just use `toEventually` or `toEventuallyNot`:
```swift
// Swift 3.0 and later
// Swift
DispatchQueue.main.async {
ocean.add("dolphins")
ocean.add("whales")
@@ -316,17 +316,6 @@ expect(ocean).toEventually(contain("dolphins", "whales"))
```
```swift
// Swift 2.3 and earlier
dispatch_async(dispatch_get_main_queue()) {
ocean.add("dolphins")
ocean.add("whales")
}
expect(ocean).toEventually(contain("dolphins", "whales"))
```
```objc
// Objective-C
@@ -857,11 +846,7 @@ Notes:
## Swift Error Handling
If you're using Swift 2.0 or newer, you can use the `throwError` matcher to check if an error is thrown.
Note:
The following code sample references the `Swift.Error` protocol.
This is `Swift.ErrorProtocol` in versions of Swift prior to version 3.0.
You can use the `throwError` matcher to check if an error is thrown.
```swift
// Swift
@@ -1277,7 +1262,7 @@ public func equal<T: Equatable>(expectedValue: T?) -> Predicate<T> {
// Predicate { actual in ... }
//
// But shown with types here for clarity.
return Predicate { (actual: Expression<T>) throws -> PredicateResult in
return Predicate { (actualExpression: Expression<T>) throws -> PredicateResult in
let msg = ExpectationMessage.expectedActualValueTo("equal <\(expectedValue)>")
if let actualValue = try actualExpression.evaluate() {
return PredicateResult(
@@ -1673,11 +1658,11 @@ backported.
The deprecating plan is a 3 major versions removal. Which is as follows:
1. Introduce new `Predicate` API, deprecation warning for old matcher APIs.
(Nimble `v7.x.x`)
(Nimble `v7.x.x` and `v8.x.x`)
2. Introduce warnings on migration-path features (`.predicate`,
`Predicate`-constructors with similar arguments to old API). (Nimble
`v8.x.x`)
3. Remove old API. (Nimble `v9.x.x`)
`v9.x.x`)
3. Remove old API. (Nimble `v10.x.x`)
# Installing Nimble
@@ -13,5 +13,6 @@ public protocol AssertionHandler {
///
/// @see AssertionHandler
public var NimbleAssertionHandler: AssertionHandler = { () -> AssertionHandler in
// swiftlint:disable:previous identifier_name
return isXCTestAvailable() ? NimbleXCTestHandler() : NimbleXCTestUnavailableHandler()
}()
@@ -37,21 +37,48 @@ public class AssertionRecorder: AssertionHandler {
}
}
extension NMBExceptionCapture {
internal func tryBlockThrows(_ unsafeBlock: () throws -> Void) throws {
var catchedError: Error?
tryBlock {
do {
try unsafeBlock()
} catch {
catchedError = error
}
}
if let error = catchedError {
throw error
}
}
}
/// Allows you to temporarily replace the current Nimble assertion handler with
/// the one provided for the scope of the closure.
///
/// Once the closure finishes, then the original Nimble assertion handler is restored.
///
/// @see AssertionHandler
public func withAssertionHandler(_ tempAssertionHandler: AssertionHandler, closure: () throws -> Void) {
public func withAssertionHandler(_ tempAssertionHandler: AssertionHandler,
file: FileString = #file,
line: UInt = #line,
closure: () throws -> Void) {
let environment = NimbleEnvironment.activeInstance
let oldRecorder = environment.assertionHandler
let capturer = NMBExceptionCapture(handler: nil, finally: ({
environment.assertionHandler = oldRecorder
}))
environment.assertionHandler = tempAssertionHandler
capturer.tryBlock {
try! closure()
do {
try capturer.tryBlockThrows {
try closure()
}
} catch {
let failureMessage = FailureMessage()
failureMessage.stringValue = "unexpected error thrown: <\(error)>"
let location = SourceLocation(file: file, line: line)
tempAssertionHandler.assert(false, message: failureMessage, location: location)
}
}
@@ -1,6 +1,6 @@
import Foundation
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
#if canImport(Darwin) && !SWIFT_PACKAGE
private func from(objcPredicate: NMBPredicate) -> Predicate<NSObject> {
return Predicate { actualExpression in
@@ -15,6 +15,7 @@ internal struct ObjCMatcherWrapper: Matcher {
func matches(_ actualExpression: Expression<NSObject>, failureMessage: FailureMessage) -> Bool {
return matcher.matches(
// swiftlint:disable:next force_try
({ try! actualExpression.evaluate() }),
failureMessage: failureMessage,
location: actualExpression.location)
@@ -22,6 +23,7 @@ internal struct ObjCMatcherWrapper: Matcher {
func doesNotMatch(_ actualExpression: Expression<NSObject>, failureMessage: FailureMessage) -> Bool {
return matcher.doesNotMatch(
// swiftlint:disable:next force_try
({ try! actualExpression.evaluate() }),
failureMessage: failureMessage,
location: actualExpression.location)
@@ -30,11 +32,13 @@ internal struct ObjCMatcherWrapper: Matcher {
// Equivalent to Expectation, but for Nimble's Objective-C interface
public class NMBExpectation: NSObject {
// swiftlint:disable identifier_name
internal let _actualBlock: () -> NSObject?
internal var _negative: Bool
internal let _file: FileString
internal let _line: UInt
internal var _timeout: TimeInterval = 1.0
// swiftlint:enable identifier_name
@objc public init(actualBlock: @escaping () -> NSObject?, negative: Bool, file: FileString, line: UInt) {
self._actualBlock = actualBlock
@@ -1,6 +1,6 @@
import Foundation
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
// swiftlint:disable line_length
public typealias MatcherBlock = (_ actualExpression: Expression<NSObject>, _ failureMessage: FailureMessage) throws -> Bool
@@ -8,8 +8,10 @@ public typealias FullMatcherBlock = (_ actualExpression: Expression<NSObject>, _
// swiftlint:enable line_length
public class NMBObjCMatcher: NSObject, NMBMatcher {
// swiftlint:disable identifier_name
let _match: MatcherBlock
let _doesNotMatch: MatcherBlock
// swiftlint:enable identifier_name
let canMatchNil: Bool
public init(canMatchNil: Bool, matcher: @escaping MatcherBlock, notMatcher: @escaping MatcherBlock) {
@@ -3,7 +3,7 @@ import Foundation
/// "Global" state of Nimble is stored here. Only DSL functions should access / be aware of this
/// class' existence
internal class NimbleEnvironment {
internal class NimbleEnvironment: NSObject {
static var activeInstance: NimbleEnvironment {
get {
let env = Thread.current.threadDictionary["NimbleEnvironment"]
@@ -20,6 +20,7 @@ internal class NimbleEnvironment {
}
}
// swiftlint:disable:next todo
// TODO: eventually migrate the global to this environment value
var assertionHandler: AssertionHandler {
get { return NimbleAssertionHandler }
@@ -29,17 +30,14 @@ internal class NimbleEnvironment {
var suppressTVOSAssertionWarning: Bool = false
var awaiter: Awaiter
init() {
let timeoutQueue: DispatchQueue
if #available(OSX 10.10, *) {
timeoutQueue = DispatchQueue.global(qos: .userInitiated)
} else {
timeoutQueue = DispatchQueue.global(priority: .high)
}
override init() {
let timeoutQueue = DispatchQueue.global(qos: .userInitiated)
awaiter = Awaiter(
waitLock: AssertionWaitLock(),
asyncQueue: .main,
timeoutQueue: timeoutQueue)
timeoutQueue: timeoutQueue
)
super.init()
}
}
@@ -64,7 +64,7 @@ class NimbleXCTestUnavailableHandler: AssertionHandler {
#endif
func isXCTestAvailable() -> Bool {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
// XCTest is weakly linked and so may not be present
return NSClassFromString("XCTestCase") != nil
#else
@@ -77,15 +77,14 @@ public func recordFailure(_ message: String, location: SourceLocation) {
XCTFail("\(message)", file: location.file, line: location.line)
#else
if let testCase = CurrentTestCaseTracker.sharedInstance.currentTestCase {
#if swift(>=4)
let line = Int(location.line)
#else
let line = location.line
#endif
testCase.recordFailure(withDescription: message, inFile: location.file, atLine: line, expected: true)
} else {
let msg = "Attempted to report a test failure to XCTest while no test case was running. " +
"The failure was:\n\"\(message)\"\nIt occurred at: \(location.file):\(location.line)"
let msg = """
Attempted to report a test failure to XCTest while no test case was running. The failure was:
\"\(message)\"
It occurred at: \(location.file):\(location.line)
"""
NSException(name: .internalInconsistencyException, reason: msg, userInfo: nil).raise()
}
#endif
+10 -4
View File
@@ -14,7 +14,7 @@ internal class NMBWait: NSObject {
// About these kind of lines, `@objc` attributes are only required for Objective-C
// support, so that should be conditional on Darwin platforms and normal Xcode builds
// (non-SwiftPM builds).
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
#if canImport(Darwin) && !SWIFT_PACKAGE
@objc
internal class func until(
timeout: TimeInterval,
@@ -87,13 +87,19 @@ internal class NMBWait: NSObject {
}
}
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
#if canImport(Darwin) && !SWIFT_PACKAGE
@objc(untilFile:line:action:)
internal class func until(_ file: FileString = #file, line: UInt = #line, action: @escaping (() -> Void) -> Void) {
internal class func until(
_ file: FileString = #file,
line: UInt = #line,
action: @escaping (@escaping () -> Void) -> Void) {
until(timeout: 1, file: file, line: line, action: action)
}
#else
internal class func until(_ file: FileString = #file, line: UInt = #line, action: @escaping (() -> Void) -> Void) {
internal class func until(
_ file: FileString = #file,
line: UInt = #line,
action: @escaping (@escaping () -> Void) -> Void) {
until(timeout: 1, file: file, line: line, action: action)
}
#endif
+11 -7
View File
@@ -43,12 +43,13 @@ internal func nimblePrecondition(
line: UInt = #line) {
let result = expr()
if !result {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
let e = NSException(
#if canImport(Darwin)
let exception = NSException(
name: NSExceptionName(name()),
reason: message(),
userInfo: nil)
e.raise()
userInfo: nil
)
exception.raise()
#else
preconditionFailure("\(name()) - \(message())", file: file, line: line)
#endif
@@ -56,9 +57,12 @@ internal func nimblePrecondition(
}
internal func internalError(_ msg: String, file: FileString = #file, line: UInt = #line) -> Never {
// swiftlint:disable line_length
fatalError(
"Nimble Bug Found: \(msg) at \(file):\(line).\n" +
"Please file a bug to Nimble: https://github.com/Quick/Nimble/issues with the " +
"code snippet that caused this error."
"""
Nimble Bug Found: \(msg) at \(file):\(line).
Please file a bug to Nimble: https://github.com/Quick/Nimble/issues with the code snippet that caused this error.
"""
)
// swiftlint:enable line_length
}
+16 -13
View File
@@ -75,6 +75,7 @@ public indirect enum ExpectationMessage {
}
internal func visitLeafs(_ f: (ExpectationMessage) -> ExpectationMessage) -> ExpectationMessage {
// swiftlint:disable:previous identifier_name
switch self {
case .fail, .expectedTo, .expectedActualValueTo, .expectedCustomValueTo:
return f(self)
@@ -90,6 +91,7 @@ public indirect enum ExpectationMessage {
/// Replaces a primary expectation with one returned by f. Preserves all composite expectations
/// that were built upon it (aka - all appended(message:) and appended(details:).
public func replacedExpectation(_ f: @escaping (ExpectationMessage) -> ExpectationMessage) -> ExpectationMessage {
// swiftlint:disable:previous identifier_name
func walk(_ msg: ExpectationMessage) -> ExpectationMessage {
switch msg {
case .fail, .expectedTo, .expectedActualValueTo, .expectedCustomValueTo:
@@ -124,6 +126,7 @@ public indirect enum ExpectationMessage {
return visitLeafs(walk)
}
// swiftlint:disable:next todo
// TODO: test & verify correct behavior
internal func prepended(message: String) -> ExpectationMessage {
return .prepends(message, self)
@@ -183,32 +186,32 @@ public indirect enum ExpectationMessage {
extension FailureMessage {
internal func toExpectationMessage() -> ExpectationMessage {
let defaultMsg = FailureMessage()
if expected != defaultMsg.expected || _stringValueOverride != nil {
let defaultMessage = FailureMessage()
if expected != defaultMessage.expected || _stringValueOverride != nil {
return .fail(stringValue)
}
var msg: ExpectationMessage = .fail(userDescription ?? "")
var message: ExpectationMessage = .fail(userDescription ?? "")
if actualValue != "" && actualValue != nil {
msg = .expectedCustomValueTo(postfixMessage, actualValue ?? "")
} else if postfixMessage != defaultMsg.postfixMessage {
message = .expectedCustomValueTo(postfixMessage, actualValue ?? "")
} else if postfixMessage != defaultMessage.postfixMessage {
if actualValue == nil {
msg = .expectedTo(postfixMessage)
message = .expectedTo(postfixMessage)
} else {
msg = .expectedActualValueTo(postfixMessage)
message = .expectedActualValueTo(postfixMessage)
}
}
if postfixActual != defaultMsg.postfixActual {
msg = .appends(msg, postfixActual)
if postfixActual != defaultMessage.postfixActual {
message = .appends(message, postfixActual)
}
if let m = extendedMessage {
msg = .details(msg, m)
if let extended = extendedMessage {
message = .details(message, extended)
}
return msg
return message
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
public class NMBExpectationMessage: NSObject {
private let msg: ExpectationMessage
+2
View File
@@ -24,8 +24,10 @@ internal func memoizedClosure<T>(_ closure: @escaping () throws -> T) -> (Bool)
/// This provides a common consumable API for matchers to utilize to allow
/// Nimble to change internals to how the captured closure is managed.
public struct Expression<T> {
// swiftlint:disable identifier_name
internal let _expression: (Bool) throws -> T?
internal let _withoutCaching: Bool
// swiftlint:enable identifier_name
public let location: SourceLocation
public let isClosure: Bool
+1
View File
@@ -28,6 +28,7 @@ public class FailureMessage: NSObject {
}
}
// swiftlint:disable:next identifier_name
internal var _stringValueOverride: String?
internal var hasOverriddenStringValue: Bool {
return _stringValueOverride != nil
+2 -1
View File
@@ -63,7 +63,7 @@ private func createPredicate<S>(_ elementMatcher: Predicate<S.Iterator.Element>)
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func allPassMatcher(_ matcher: NMBMatcher) -> NMBPredicate {
return NMBPredicate { actualExpression in
@@ -103,6 +103,7 @@ extension NMBObjCMatcher {
} else {
let failureMessage = FailureMessage()
let result = matcher.matches(
// swiftlint:disable:next force_try
({ try! expr.evaluate() }),
failureMessage: failureMessage,
location: expr.location
+10 -4
View File
@@ -23,14 +23,18 @@ private func async<T>(style: ExpectationStyle, predicate: Predicate<T>, timeout:
}
switch result {
case .completed: return lastPredicateResult!
case .timedOut: return PredicateResult(status: .fail, message: lastPredicateResult!.message)
case .timedOut:
let message = lastPredicateResult?.message ?? .fail("timed out before returning a value")
return PredicateResult(status: .fail, message: message)
case let .errorThrown(error):
return PredicateResult(status: .fail, message: .fail("unexpected error thrown: <\(error)>"))
case let .raisedException(exception):
return PredicateResult(status: .fail, message: .fail("unexpected exception raised: \(exception)"))
case .blockedRunLoop:
// swiftlint:disable:next line_length
return PredicateResult(status: .fail, message: lastPredicateResult!.message.appended(message: " (timed out, but main thread was unresponsive)."))
let message = lastPredicateResult?.message.appended(message: " (timed out, but main run loop was unresponsive).") ??
.fail("main run loop was unresponsive")
return PredicateResult(status: .fail, message: message)
case .incomplete:
internalError("Reached .incomplete state for \(fnName)(...).")
}
@@ -38,8 +42,10 @@ private func async<T>(style: ExpectationStyle, predicate: Predicate<T>, timeout:
}
private let toEventuallyRequiresClosureError = FailureMessage(
// swiftlint:disable:next line_length
stringValue: "expect(...).toEventually(...) requires an explicit closure (eg - expect { ... }.toEventually(...) )\nSwift 1.2 @autoclosure behavior has changed in an incompatible way for Nimble to function"
stringValue: """
expect(...).toEventually(...) requires an explicit closure (eg - expect { ... }.toEventually(...) )
Swift 1.2 @autoclosure behavior has changed in an incompatible way for Nimble to function
"""
)
extension Expectation {
+1 -1
View File
@@ -29,7 +29,7 @@ public func beAKindOf<T>(_ expectedType: T.Type) -> Predicate<Any> {
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
/// A Nimble matcher that succeeds when the actual value is an instance of the given class.
/// @see beAnInstanceOf if you want to match against the exact class
@@ -33,7 +33,7 @@ public func beAnInstanceOf(_ expectedClass: AnyClass) -> Predicate<NSObject> {
} else {
actualString = "<nil>"
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
let matches = instance != nil && instance!.isMember(of: expectedClass)
#else
let matches = instance != nil && type(of: instance!) == expectedClass
@@ -45,7 +45,7 @@ public func beAnInstanceOf(_ expectedClass: AnyClass) -> Predicate<NSObject> {
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func beAnInstanceOfMatcher(_ expected: AnyClass) -> NMBMatcher {
return NMBPredicate { actualExpression in
+8 -1
View File
@@ -1,5 +1,6 @@
import Foundation
// swiftlint:disable:next identifier_name
public let DefaultDelta = 0.0001
internal func isCloseTo(_ actualValue: NMBDoubleConvertible?,
@@ -34,10 +35,12 @@ public func beCloseTo(_ expectedValue: NMBDoubleConvertible, within delta: Doubl
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
public class NMBObjCBeCloseToMatcher: NSObject, NMBMatcher {
// swiftlint:disable identifier_name
var _expected: NSNumber
var _delta: CDouble
// swiftlint:enable identifier_name
init(expected: NSNumber, within: CDouble) {
_expected = expected
_delta = within
@@ -110,14 +113,17 @@ public func beCloseTo(_ expectedValues: [Double], within delta: Double = Default
infix operator : ComparisonPrecedence
// swiftlint:disable:next identifier_name
public func (lhs: Expectation<[Double]>, rhs: [Double]) {
lhs.to(beCloseTo(rhs))
}
// swiftlint:disable:next identifier_name
public func (lhs: Expectation<NMBDoubleConvertible>, rhs: NMBDoubleConvertible) {
lhs.to(beCloseTo(rhs))
}
// swiftlint:disable:next identifier_name
public func (lhs: Expectation<NMBDoubleConvertible>, rhs: (expected: NMBDoubleConvertible, delta: Double)) {
lhs.to(beCloseTo(rhs.expected, within: rhs.delta))
}
@@ -133,6 +139,7 @@ precedencegroup PlusMinusOperatorPrecedence {
}
infix operator ± : PlusMinusOperatorPrecedence
// swiftlint:disable:next identifier_name
public func ±(lhs: NMBDoubleConvertible, rhs: Double) -> (expected: NMBDoubleConvertible, delta: Double) {
return (expected: lhs, delta: rhs)
}
+25 -4
View File
@@ -4,15 +4,36 @@ import Foundation
/// means the are no items in that collection. For strings, it is an empty string.
public func beEmpty<S: Sequence>() -> Predicate<S> {
return Predicate.simple("be empty") { actualExpression in
let actualSeq = try actualExpression.evaluate()
if actualSeq == nil {
guard let actual = try actualExpression.evaluate() else {
return .fail
}
var generator = actualSeq!.makeIterator()
var generator = actual.makeIterator()
return PredicateStatus(bool: generator.next() == nil)
}
}
/// A Nimble matcher that succeeds when a value is "empty". For collections, this
/// means the are no items in that collection. For strings, it is an empty string.
public func beEmpty<S: SetAlgebra>() -> Predicate<S> {
return Predicate.simple("be empty") { actualExpression in
guard let actual = try actualExpression.evaluate() else {
return .fail
}
return PredicateStatus(bool: actual.isEmpty)
}
}
/// A Nimble matcher that succeeds when a value is "empty". For collections, this
/// means the are no items in that collection. For strings, it is an empty string.
public func beEmpty<S: Sequence & SetAlgebra>() -> Predicate<S> {
return Predicate.simple("be empty") { actualExpression in
guard let actual = try actualExpression.evaluate() else {
return .fail
}
return PredicateStatus(bool: actual.isEmpty)
}
}
/// A Nimble matcher that succeeds when a value is "empty". For collections, this
/// means the are no items in that collection. For strings, it is an empty string.
public func beEmpty() -> Predicate<String> {
@@ -61,7 +82,7 @@ public func beEmpty() -> Predicate<NMBCollection> {
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func beEmptyMatcher() -> NMBPredicate {
return NMBPredicate { actualExpression in
@@ -30,12 +30,12 @@ public func > (lhs: Expectation<NMBComparable>, rhs: NMBComparable?) {
lhs.to(beGreaterThan(rhs))
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func beGreaterThanMatcher(_ expected: NMBComparable?) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
@objc public class func beGreaterThanMatcher(_ expected: NMBComparable?) -> NMBMatcher {
return NMBPredicate { actualExpression in
let expr = actualExpression.cast { $0 as? NMBComparable }
return try beGreaterThan(expected).matches(expr, failureMessage: failureMessage)
return try beGreaterThan(expected).satisfies(expr).toObjectiveC()
}
}
}
@@ -32,12 +32,12 @@ public func >=<T: NMBComparable>(lhs: Expectation<T>, rhs: T) {
lhs.to(beGreaterThanOrEqualTo(rhs))
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func beGreaterThanOrEqualToMatcher(_ expected: NMBComparable?) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
@objc public class func beGreaterThanOrEqualToMatcher(_ expected: NMBComparable?) -> NMBMatcher {
return NMBPredicate { actualExpression in
let expr = actualExpression.cast { $0 as? NMBComparable }
return try beGreaterThanOrEqualTo(expected).matches(expr, failureMessage: failureMessage)
return try beGreaterThanOrEqualTo(expected).satisfies(expr).toObjectiveC()
}
}
}
@@ -4,14 +4,14 @@ import Foundation
/// as the expected instance.
public func beIdenticalTo(_ expected: Any?) -> Predicate<Any> {
return Predicate.define { actualExpression in
#if os(Linux)
#if os(Linux) && !swift(>=4.1.50)
let actual = try actualExpression.evaluate() as? AnyObject
#else
let actual = try actualExpression.evaluate() as AnyObject?
#endif
let bool: Bool
#if os(Linux)
#if os(Linux) && !swift(>=4.1.50)
bool = actual === (expected as? AnyObject) && actual !== nil
#else
bool = actual === (expected as AnyObject?) && actual !== nil
@@ -41,12 +41,12 @@ public func be(_ expected: Any?) -> Predicate<Any> {
return beIdenticalTo(expected)
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func beIdenticalToMatcher(_ expected: NSObject?) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
@objc public class func beIdenticalToMatcher(_ expected: NSObject?) -> NMBMatcher {
return NMBPredicate { actualExpression in
let aExpr = actualExpression.cast { $0 as Any? }
return try beIdenticalTo(expected).matches(aExpr, failureMessage: failureMessage)
return try beIdenticalTo(expected).satisfies(aExpr).toObjectiveC()
}
}
}
@@ -29,12 +29,12 @@ public func < (lhs: Expectation<NMBComparable>, rhs: NMBComparable?) {
lhs.to(beLessThan(rhs))
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func beLessThanMatcher(_ expected: NMBComparable?) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
@objc public class func beLessThanMatcher(_ expected: NMBComparable?) -> NMBMatcher {
return NMBPredicate { actualExpression in
let expr = actualExpression.cast { $0 as? NMBComparable }
return try beLessThan(expected).matches(expr, failureMessage: failureMessage)
return try beLessThan(expected).satisfies(expr).toObjectiveC()
}
}
}
@@ -29,12 +29,12 @@ public func <=<T: NMBComparable>(lhs: Expectation<T>, rhs: T) {
lhs.to(beLessThanOrEqualTo(rhs))
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func beLessThanOrEqualToMatcher(_ expected: NMBComparable?) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
@objc public class func beLessThanOrEqualToMatcher(_ expected: NMBComparable?) -> NMBMatcher {
return NMBPredicate { actualExpression in
let expr = actualExpression.cast { $0 as? NMBComparable }
return try beLessThanOrEqualTo(expected).matches(expr, failureMessage: failureMessage)
return try beLessThanOrEqualTo(expected).satisfies(expr).toObjectiveC()
}
}
}
+17 -30
View File
@@ -100,14 +100,6 @@ public func beTruthy<T: ExpressibleByBooleanLiteral & Equatable>() -> Predicate<
return Predicate.simpleNilable("be truthy") { actualExpression in
let actualValue = try actualExpression.evaluate()
if let actualValue = actualValue {
// FIXME: This is a workaround to SR-2290.
// See:
// - https://bugs.swift.org/browse/SR-2290
// - https://github.com/norio-nomura/Nimble/pull/5#issuecomment-237835873
if let number = actualValue as? NSNumber {
return PredicateStatus(bool: number.boolValue == true)
}
return PredicateStatus(bool: actualValue == (true as T))
}
return PredicateStatus(bool: actualValue != nil)
@@ -120,47 +112,42 @@ public func beFalsy<T: ExpressibleByBooleanLiteral & Equatable>() -> Predicate<T
return Predicate.simpleNilable("be falsy") { actualExpression in
let actualValue = try actualExpression.evaluate()
if let actualValue = actualValue {
// FIXME: This is a workaround to SR-2290.
// See:
// - https://bugs.swift.org/browse/SR-2290
// - https://github.com/norio-nomura/Nimble/pull/5#issuecomment-237835873
if let number = actualValue as? NSNumber {
return PredicateStatus(bool: number.boolValue == false)
}
return PredicateStatus(bool: actualValue == (false as T))
}
return PredicateStatus(bool: actualValue == nil)
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func beTruthyMatcher() -> NMBObjCMatcher {
return NMBObjCMatcher { actualExpression, failureMessage in
@objc public class func beTruthyMatcher() -> NMBMatcher {
return NMBPredicate { actualExpression in
let expr = actualExpression.cast { ($0 as? NSNumber)?.boolValue ?? false }
return try beTruthy().matches(expr, failureMessage: failureMessage)
return try beTruthy().satisfies(expr).toObjectiveC()
}
}
@objc public class func beFalsyMatcher() -> NMBObjCMatcher {
return NMBObjCMatcher { actualExpression, failureMessage in
@objc public class func beFalsyMatcher() -> NMBMatcher {
return NMBPredicate { actualExpression in
let expr = actualExpression.cast { ($0 as? NSNumber)?.boolValue ?? false }
return try beFalsy().matches(expr, failureMessage: failureMessage)
return try beFalsy().satisfies(expr).toObjectiveC()
}
}
@objc public class func beTrueMatcher() -> NMBObjCMatcher {
return NMBObjCMatcher { actualExpression, failureMessage in
@objc public class func beTrueMatcher() -> NMBMatcher {
return NMBPredicate { actualExpression in
let expr = actualExpression.cast { ($0 as? NSNumber)?.boolValue ?? false }
return try beTrue().matches(expr, failureMessage: failureMessage)
return try beTrue().satisfies(expr).toObjectiveC()
}
}
@objc public class func beFalseMatcher() -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
let expr = actualExpression.cast { ($0 as? NSNumber)?.boolValue ?? false }
return try beFalse().matches(expr, failureMessage: failureMessage)
@objc public class func beFalseMatcher() -> NMBMatcher {
return NMBPredicate { actualExpression in
let expr = actualExpression.cast { value -> Bool? in
guard let value = value else { return nil }
return (value as? NSNumber)?.boolValue ?? false
}
return try beFalse().satisfies(expr).toObjectiveC()
}
}
}
+4 -4
View File
@@ -8,11 +8,11 @@ public func beNil<T>() -> Predicate<T> {
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func beNilMatcher() -> NMBObjCMatcher {
return NMBObjCMatcher { actualExpression, failureMessage in
return try beNil().matches(actualExpression, failureMessage: failureMessage)
@objc public class func beNilMatcher() -> NMBMatcher {
return NMBPredicate { actualExpression in
return try beNil().satisfies(actualExpression).toObjectiveC()
}
}
}
+7 -5
View File
@@ -8,10 +8,12 @@ public func beVoid() -> Predicate<()> {
}
}
public func == (lhs: Expectation<()>, rhs: ()) {
lhs.to(beVoid())
}
extension Expectation where T == () {
public static func == (lhs: Expectation<()>, rhs: ()) {
lhs.to(beVoid())
}
public func != (lhs: Expectation<()>, rhs: ()) {
lhs.toNot(beVoid())
public static func != (lhs: Expectation<()>, rhs: ()) {
lhs.toNot(beVoid())
}
}
+8 -8
View File
@@ -35,24 +35,24 @@ public func beginWith(_ startingElement: Any) -> Predicate<NMBOrderedCollection>
public func beginWith(_ startingSubstring: String) -> Predicate<String> {
return Predicate.simple("begin with <\(startingSubstring)>") { actualExpression in
if let actual = try actualExpression.evaluate() {
let range = actual.range(of: startingSubstring)
return PredicateStatus(bool: range != nil && range!.lowerBound == actual.startIndex)
return PredicateStatus(bool: actual.hasPrefix(startingSubstring))
}
return .fail
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func beginWithMatcher(_ expected: Any) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
@objc public class func beginWithMatcher(_ expected: Any) -> NMBMatcher {
return NMBPredicate { actualExpression in
let actual = try actualExpression.evaluate()
if (actual as? String) != nil {
if actual is String {
let expr = actualExpression.cast { $0 as? String }
return try beginWith(expected as! String).matches(expr, failureMessage: failureMessage)
// swiftlint:disable:next force_cast
return try beginWith(expected as! String).satisfies(expr).toObjectiveC()
} else {
let expr = actualExpression.cast { $0 as? NMBOrderedCollection }
return try beginWith(expected).matches(expr, failureMessage: failureMessage)
return try beginWith(expected).satisfies(expr).toObjectiveC()
}
}
}
+67 -18
View File
@@ -1,16 +1,17 @@
import Foundation
/// A Nimble matcher that succeeds when the actual sequence contains the expected value.
/// A Nimble matcher that succeeds when the actual sequence contains the expected values.
public func contain<S: Sequence, T: Equatable>(_ items: T...) -> Predicate<S>
where S.Iterator.Element == T {
where S.Element == T {
return contain(items)
}
/// A Nimble matcher that succeeds when the actual sequence contains the expected values.
public func contain<S: Sequence, T: Equatable>(_ items: [T]) -> Predicate<S>
where S.Iterator.Element == T {
where S.Element == T {
return Predicate.simple("contain <\(arrayAsString(items))>") { actualExpression in
if let actual = try actualExpression.evaluate() {
let matches = items.all {
let matches = items.allSatisfy {
return actual.contains($0)
}
return PredicateStatus(bool: matches)
@@ -19,6 +20,46 @@ public func contain<S: Sequence, T: Equatable>(_ items: [T]) -> Predicate<S>
}
}
/// A Nimble matcher that succeeds when the actual set contains the expected values.
public func contain<S: SetAlgebra, T: Equatable>(_ items: T...) -> Predicate<S>
where S.Element == T {
return contain(items)
}
/// A Nimble matcher that succeeds when the actual set contains the expected values.
public func contain<S: SetAlgebra, T: Equatable>(_ items: [T]) -> Predicate<S>
where S.Element == T {
return Predicate.simple("contain <\(arrayAsString(items))>") { actualExpression in
if let actual = try actualExpression.evaluate() {
let matches = items.allSatisfy {
return actual.contains($0)
}
return PredicateStatus(bool: matches)
}
return .fail
}
}
/// A Nimble matcher that succeeds when the actual set contains the expected values.
public func contain<S: Sequence & SetAlgebra, T: Equatable>(_ items: T...) -> Predicate<S>
where S.Element == T {
return contain(items)
}
/// A Nimble matcher that succeeds when the actual set contains the expected values.
public func contain<S: Sequence & SetAlgebra, T: Equatable>(_ items: [T]) -> Predicate<S>
where S.Element == T {
return Predicate.simple("contain <\(arrayAsString(items))>") { actualExpression in
if let actual = try actualExpression.evaluate() {
let matches = items.allSatisfy {
return actual.contains($0)
}
return PredicateStatus(bool: matches)
}
return .fail
}
}
/// A Nimble matcher that succeeds when the actual string contains the expected substring.
public func contain(_ substrings: String...) -> Predicate<String> {
return contain(substrings)
@@ -27,7 +68,7 @@ public func contain(_ substrings: String...) -> Predicate<String> {
public func contain(_ substrings: [String]) -> Predicate<String> {
return Predicate.simple("contain <\(arrayAsString(substrings))>") { actualExpression in
if let actual = try actualExpression.evaluate() {
let matches = substrings.all {
let matches = substrings.allSatisfy {
let range = actual.range(of: $0)
return range != nil && !range!.isEmpty
}
@@ -45,7 +86,7 @@ public func contain(_ substrings: NSString...) -> Predicate<NSString> {
public func contain(_ substrings: [NSString]) -> Predicate<NSString> {
return Predicate.simple("contain <\(arrayAsString(substrings))>") { actualExpression in
if let actual = try actualExpression.evaluate() {
let matches = substrings.all { actual.range(of: $0.description).length != 0 }
let matches = substrings.allSatisfy { actual.range(of: $0.description).length != 0 }
return PredicateStatus(bool: matches)
}
return .fail
@@ -60,17 +101,17 @@ public func contain(_ items: Any?...) -> Predicate<NMBContainer> {
public func contain(_ items: [Any?]) -> Predicate<NMBContainer> {
return Predicate.simple("contain <\(arrayAsString(items))>") { actualExpression in
guard let actual = try actualExpression.evaluate() else { return .fail }
let matches = items.all { item in
let matches = items.allSatisfy { item in
return item.map { actual.contains($0) } ?? false
}
return PredicateStatus(bool: matches)
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func containMatcher(_ expected: [NSObject]) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
@objc public class func containMatcher(_ expected: [NSObject]) -> NMBMatcher {
return NMBPredicate { actualExpression in
let location = actualExpression.location
let actualValue = try actualExpression.evaluate()
if let value = actualValue as? NMBContainer {
@@ -78,17 +119,25 @@ extension NMBObjCMatcher {
// A straightforward cast on the array causes this to crash, so we have to cast the individual items
let expectedOptionals: [Any?] = expected.map({ $0 as Any? })
return try contain(expectedOptionals).matches(expr, failureMessage: failureMessage)
return try contain(expectedOptionals).satisfies(expr).toObjectiveC()
} else if let value = actualValue as? NSString {
let expr = Expression(expression: ({ value as String }), location: location)
return try contain(expected as! [String]).matches(expr, failureMessage: failureMessage)
} else if actualValue != nil {
// swiftlint:disable:next line_length
failureMessage.postfixMessage = "contain <\(arrayAsString(expected))> (only works for NSArrays, NSSets, NSHashTables, and NSStrings)"
} else {
failureMessage.postfixMessage = "contain <\(arrayAsString(expected))>"
// swiftlint:disable:next force_cast
return try contain(expected as! [String]).satisfies(expr).toObjectiveC()
}
return false
let message: ExpectationMessage
if actualValue != nil {
message = ExpectationMessage.expectedActualValueTo(
// swiftlint:disable:next line_length
"contain <\(arrayAsString(expected))> (only works for NSArrays, NSSets, NSHashTables, and NSStrings)"
)
} else {
message = ExpectationMessage
.expectedActualValueTo("contain <\(arrayAsString(expected))>")
.appendedBeNilHint()
}
return NMBPredicateResult(status: .fail, message: message.toObjectiveC())
}
}
}
@@ -24,20 +24,22 @@ public func containElementSatisfying<S: Sequence, T>(_ predicate: @escaping ((T)
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func containElementSatisfyingMatcher(_ predicate: @escaping ((NSObject) -> Bool)) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
@objc public class func containElementSatisfyingMatcher(_ predicate: @escaping ((NSObject) -> Bool)) -> NMBMatcher {
return NMBPredicate { actualExpression in
let value = try actualExpression.evaluate()
guard let enumeration = value as? NSFastEnumeration else {
// swiftlint:disable:next line_length
failureMessage.postfixMessage = "containElementSatisfying must be provided an NSFastEnumeration object"
failureMessage.actualValue = nil
failureMessage.expected = ""
failureMessage.to = ""
return false
let message = ExpectationMessage.fail(
"containElementSatisfying must be provided an NSFastEnumeration object"
)
return NMBPredicateResult(status: .fail, message: message.toObjectiveC())
}
let message = ExpectationMessage
.expectedTo("find object in collection that satisfies predicate")
.toObjectiveC()
var iterator = NSFastEnumerationIterator(enumeration)
while let item = iterator.next() {
guard let object = item as? NSObject else {
@@ -45,13 +47,11 @@ public func containElementSatisfying<S: Sequence, T>(_ predicate: @escaping ((T)
}
if predicate(object) {
return true
return NMBPredicateResult(status: .matches, message: message)
}
}
failureMessage.actualValue = nil
failureMessage.postfixMessage = "find object in collection that satisfies predicate"
return false
return NMBPredicateResult(status: .doesNotMatch, message: message)
}
}
}
@@ -0,0 +1,16 @@
/// A Nimble matcher that succeeds when the actual sequence contain the same elements in the same order to the exepected sequence.
public func elementsEqual<S: Sequence>(_ expectedValue: S?) -> Predicate<S> where S.Element: Equatable {
// A matcher abstraction for https://developer.apple.com/documentation/swift/sequence/2949668-elementsequal
return Predicate.define("elementsEqual <\(stringify(expectedValue))>") { (actualExpression, msg) in
let actualValue = try actualExpression.evaluate()
switch (expectedValue, actualValue) {
case (nil, _?):
return PredicateResult(status: .fail, message: msg.appendedBeNilHint())
case (nil, nil), (_, nil):
return PredicateResult(status: .fail, message: msg)
case (let expected?, let actual?):
let matches = expected.elementsEqual(actual)
return PredicateResult(bool: matches, message: msg)
}
}
}
+7 -6
View File
@@ -50,17 +50,18 @@ public func endWith(_ endingSubstring: String) -> Predicate<String> {
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func endWithMatcher(_ expected: Any) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
@objc public class func endWithMatcher(_ expected: Any) -> NMBMatcher {
return NMBPredicate { actualExpression in
let actual = try actualExpression.evaluate()
if (actual as? String) != nil {
if actual is String {
let expr = actualExpression.cast { $0 as? String }
return try endWith(expected as! String).matches(expr, failureMessage: failureMessage)
// swiftlint:disable:next force_cast
return try endWith(expected as! String).satisfies(expr).toObjectiveC()
} else {
let expr = actualExpression.cast { $0 as? NMBOrderedCollection }
return try endWith(expected).matches(expr, failureMessage: failureMessage)
return try endWith(expected).satisfies(expr).toObjectiveC()
}
}
}
+46 -118
View File
@@ -7,103 +7,30 @@ import Foundation
public func equal<T: Equatable>(_ expectedValue: T?) -> Predicate<T> {
return Predicate.define("equal <\(stringify(expectedValue))>") { actualExpression, msg in
let actualValue = try actualExpression.evaluate()
let matches = actualValue == expectedValue && expectedValue != nil
if expectedValue == nil || actualValue == nil {
if expectedValue == nil && actualValue != nil {
return PredicateResult(
status: .fail,
message: msg.appendedBeNilHint()
)
}
switch (expectedValue, actualValue) {
case (nil, _?):
return PredicateResult(status: .fail, message: msg.appendedBeNilHint())
case (nil, nil), (_, nil):
return PredicateResult(status: .fail, message: msg)
case (let expected?, let actual?):
let matches = expected == actual
return PredicateResult(bool: matches, message: msg)
}
return PredicateResult(status: PredicateStatus(bool: matches), message: msg)
}
}
/// A Nimble matcher that succeeds when the actual value is equal to the expected value.
/// Values can support equal by supporting the Equatable protocol.
///
/// @see beCloseTo if you want to match imprecise types (eg - floats, doubles).
public func equal<T, C: Equatable>(_ expectedValue: [T: C]?) -> Predicate<[T: C]> {
return Predicate.define("equal <\(stringify(expectedValue))>") { actualExpression, msg in
let actualValue = try actualExpression.evaluate()
if expectedValue == nil || actualValue == nil {
if expectedValue == nil && actualValue != nil {
return PredicateResult(
status: .fail,
message: msg.appendedBeNilHint()
)
}
return PredicateResult(status: .fail, message: msg)
}
return PredicateResult(
status: PredicateStatus(bool: expectedValue! == actualValue!),
message: msg
)
}
}
/// A Nimble matcher that succeeds when the actual collection is equal to the expected collection.
/// Items must implement the Equatable protocol.
public func equal<T: Equatable>(_ expectedValue: [T]?) -> Predicate<[T]> {
return Predicate.define("equal <\(stringify(expectedValue))>") { actualExpression, msg in
let actualValue = try actualExpression.evaluate()
if expectedValue == nil || actualValue == nil {
if expectedValue == nil && actualValue != nil {
return PredicateResult(
status: .fail,
message: msg.appendedBeNilHint()
)
}
return PredicateResult(
status: .fail,
message: msg
)
}
return PredicateResult(
bool: expectedValue! == actualValue!,
message: msg
)
}
}
/// A Nimble matcher allowing comparison of collection with optional type
public func equal<T: Equatable>(_ expectedValue: [T?]) -> Predicate<[T?]> {
return Predicate.define("equal <\(stringify(expectedValue))>") { actualExpression, msg in
if let actualValue = try actualExpression.evaluate() {
let doesNotMatch = PredicateResult(
status: .doesNotMatch,
message: msg
)
if expectedValue.count != actualValue.count {
return doesNotMatch
}
for (index, item) in actualValue.enumerated() {
let otherItem = expectedValue[index]
if item == nil && otherItem == nil {
continue
} else if item == nil && otherItem != nil {
return doesNotMatch
} else if item != nil && otherItem == nil {
return doesNotMatch
} else if item! != otherItem! {
return doesNotMatch
}
}
return PredicateResult(
status: .matches,
message: msg
)
} else {
guard let actualValue = try actualExpression.evaluate() else {
return PredicateResult(
status: .fail,
message: msg.appendedBeNilHint()
)
}
let matches = expectedValue == actualValue
return PredicateResult(bool: matches, message: msg)
}
}
@@ -128,44 +55,45 @@ private func equal<T>(_ expectedValue: Set<T>?, stringify: @escaping (Set<T>?) -
var errorMessage: ExpectationMessage =
.expectedActualValueTo("equal <\(stringify(expectedValue))>")
if let expectedValue = expectedValue {
if let actualValue = try actualExpression.evaluate() {
errorMessage = .expectedCustomValueTo(
"equal <\(stringify(expectedValue))>",
"<\(stringify(actualValue))>"
)
if expectedValue == actualValue {
return PredicateResult(
status: .matches,
message: errorMessage
)
}
let missing = expectedValue.subtracting(actualValue)
if missing.count > 0 {
errorMessage = errorMessage.appended(message: ", missing <\(stringify(missing))>")
}
let extra = actualValue.subtracting(expectedValue)
if extra.count > 0 {
errorMessage = errorMessage.appended(message: ", extra <\(stringify(extra))>")
}
return PredicateResult(
status: .doesNotMatch,
message: errorMessage
)
}
return PredicateResult(
status: .fail,
message: errorMessage.appendedBeNilHint()
)
} else {
guard let expectedValue = expectedValue else {
return PredicateResult(
status: .fail,
message: errorMessage.appendedBeNilHint()
)
}
guard let actualValue = try actualExpression.evaluate() else {
return PredicateResult(
status: .fail,
message: errorMessage.appendedBeNilHint()
)
}
errorMessage = .expectedCustomValueTo(
"equal <\(stringify(expectedValue))>",
"<\(stringify(actualValue))>"
)
if expectedValue == actualValue {
return PredicateResult(
status: .matches,
message: errorMessage
)
}
let missing = expectedValue.subtracting(actualValue)
if missing.count > 0 {
errorMessage = errorMessage.appended(message: ", missing <\(stringify(missing))>")
}
let extra = actualValue.subtracting(expectedValue)
if extra.count > 0 {
errorMessage = errorMessage.appended(message: ", extra <\(stringify(extra))>")
}
return PredicateResult(
status: .doesNotMatch,
message: errorMessage
)
}
}
@@ -209,7 +137,7 @@ public func !=<T, C: Equatable>(lhs: Expectation<[T: C]>, rhs: [T: C]?) {
lhs.toNot(equal(rhs))
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func equalMatcher(_ expected: NSObject) -> NMBMatcher {
return NMBPredicate { actualExpression in
+18 -9
View File
@@ -7,7 +7,7 @@ import Foundation
/// A Nimble matcher that succeeds when the actual Collection's count equals
/// the expected value
public func haveCount<T: Collection>(_ expectedValue: T.IndexDistance) -> Predicate<T> {
public func haveCount<T: Collection>(_ expectedValue: Int) -> Predicate<T> {
return Predicate.define { actualExpression in
if let actualValue = try actualExpression.evaluate() {
let message = ExpectationMessage
@@ -45,20 +45,29 @@ public func haveCount(_ expectedValue: Int) -> Predicate<NMBCollection> {
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func haveCountMatcher(_ expected: NSNumber) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
@objc public class func haveCountMatcher(_ expected: NSNumber) -> NMBMatcher {
return NMBPredicate { actualExpression in
let location = actualExpression.location
let actualValue = try actualExpression.evaluate()
if let value = actualValue as? NMBCollection {
let expr = Expression(expression: ({ value as NMBCollection}), location: location)
return try haveCount(expected.intValue).matches(expr, failureMessage: failureMessage)
} else if let actualValue = actualValue {
failureMessage.postfixMessage = "get type of NSArray, NSSet, NSDictionary, or NSHashTable"
failureMessage.actualValue = "\(String(describing: type(of: actualValue)))"
return try haveCount(expected.intValue).satisfies(expr).toObjectiveC()
}
return false
let message: ExpectationMessage
if let actualValue = actualValue {
message = ExpectationMessage.expectedCustomValueTo(
"get type of NSArray, NSSet, NSDictionary, or NSHashTable",
"\(String(describing: type(of: actualValue)))"
)
} else {
message = ExpectationMessage
.expectedActualValueTo("have a collection with count \(stringify(expected.intValue))")
.appendedBeNilHint()
}
return NMBPredicateResult(status: .fail, message: message.toObjectiveC())
}
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ public func match(_ expectedValue: String?) -> Predicate<String> {
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func matchMatcher(_ expected: NSString) -> NMBMatcher {
@@ -1,6 +1,6 @@
import Foundation
// `CGFloat` is in Foundation (swift-corelibs-foundation) on Linux.
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
import CoreGraphics
#endif
@@ -28,7 +28,7 @@ extension Matcher {
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
/// Objective-C interface to the Swift variant of Matcher.
@objc public protocol NMBMatcher {
func matches(_ actualBlock: @escaping () -> NSObject?, failureMessage: FailureMessage, location: SourceLocation) -> Bool
@@ -41,7 +41,8 @@ public protocol NMBContainer {
func contains(_ anObject: Any) -> Bool
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
// swiftlint:disable:next todo
// FIXME: NSHashTable can not conform to NMBContainer since swift-DEVELOPMENT-SNAPSHOT-2016-04-25-a
//extension NSHashTable : NMBContainer {} // Corelibs Foundation does not include this class yet
#endif
@@ -54,7 +55,7 @@ public protocol NMBCollection {
var count: Int { get }
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NSHashTable: NMBCollection {} // Corelibs Foundation does not include these classes yet
extension NSMapTable: NMBCollection {}
#endif
@@ -131,7 +132,7 @@ extension NSDate: TestOutputStringConvertible {
/// beGreaterThan(), beGreaterThanOrEqualTo(), and equal() matchers.
///
/// Types that conform to Swift's Comparable protocol will work implicitly too
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
@objc public protocol NMBComparable {
func NMB_compare(_ otherObject: NMBComparable!) -> ComparisonResult
}
@@ -144,11 +145,13 @@ public protocol NMBComparable {
extension NSNumber: NMBComparable {
public func NMB_compare(_ otherObject: NMBComparable!) -> ComparisonResult {
// swiftlint:disable:next force_cast
return compare(otherObject as! NSNumber)
}
}
extension NSString: NMBComparable {
public func NMB_compare(_ otherObject: NMBComparable!) -> ComparisonResult {
// swiftlint:disable:next force_cast
return compare(otherObject as! String)
}
}
@@ -1,36 +1,9 @@
import Foundation
// A workaround to SR-6419.
extension NotificationCenter {
#if !(os(macOS) || os(iOS) || os(tvOS) || os(watchOS))
#if swift(>=4.0)
#if swift(>=4.0.2)
#else
func addObserver(forName name: Notification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> Void) -> NSObjectProtocol {
return addObserver(forName: name, object: obj, queue: queue, usingBlock: block)
}
#endif
#elseif swift(>=3.2)
#if swift(>=3.2.2)
#else
// swiftlint:disable:next line_length
func addObserver(forName name: Notification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> Void) -> NSObjectProtocol {
return addObserver(forName: name, object: obj, queue: queue, usingBlock: block)
}
#endif
#else
// swiftlint:disable:next line_length
func addObserver(forName name: Notification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> Void) -> NSObjectProtocol {
return addObserver(forName: name, object: obj, queue: queue, usingBlock: block)
}
#endif
#endif
}
internal class NotificationCollector {
private(set) var observedNotifications: [Notification]
private let notificationCenter: NotificationCenter
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
private var token: AnyObject?
#else
private var token: NSObjectProtocol?
@@ -43,14 +16,14 @@ internal class NotificationCollector {
func startObserving() {
// swiftlint:disable:next line_length
self.token = self.notificationCenter.addObserver(forName: nil, object: nil, queue: nil, using: { [weak self] n in
self.token = self.notificationCenter.addObserver(forName: nil, object: nil, queue: nil) { [weak self] notification in
// linux-swift gets confused by .append(n)
self?.observedNotifications.append(n)
})
self?.observedNotifications.append(notification)
}
}
deinit {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
if let token = self.token {
self.notificationCenter.removeObserver(token)
}
+3 -2
View File
@@ -218,6 +218,7 @@ extension Predicate: Matcher {
extension Predicate {
// Someday, make this public? Needs documentation
internal func after(f: @escaping (Expression<T>, PredicateResult) throws -> PredicateResult) -> Predicate<T> {
// swiftlint:disable:previous identifier_name
return Predicate { actual -> PredicateResult in
let result = try self.satisfies(actual)
return try f(actual, result)
@@ -241,7 +242,7 @@ extension Predicate {
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
public typealias PredicateBlock = (_ actualExpression: Expression<NSObject>) throws -> NMBPredicateResult
public class NMBPredicate: NSObject {
@@ -311,7 +312,7 @@ final public class NMBPredicateStatus: NSObject {
public static let doesNotMatch: NMBPredicateStatus = NMBPredicateStatus(status: 1)
public static let fail: NMBPredicateStatus = NMBPredicateStatus(status: 2)
public override var hashValue: Int { return self.status.hashValue }
public override var hash: Int { return self.status.hashValue }
public override func isEqual(_ object: Any?) -> Bool {
guard let otherPredicate = object as? NMBPredicateStatus else {
@@ -1,7 +1,7 @@
import Foundation
// This matcher requires the Objective-C, and being built by Xcode rather than the Swift Package Manager
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
#if canImport(Darwin) && !SWIFT_PACKAGE
/// A Nimble matcher that succeeds when the actual expression raises an
/// exception with the specified name, reason, and/or userInfo.
@@ -23,8 +23,12 @@ public func raiseException(
exception = e
}), finally: nil)
capture.tryBlock {
_ = try! actualExpression.evaluate()
do {
try capture.tryBlockThrows {
_ = try actualExpression.evaluate()
}
} catch {
return PredicateResult(status: .fail, message: .fail("unexpected error thrown: <\(error)>"))
}
let failureMessage = FailureMessage()
@@ -118,10 +122,12 @@ internal func exceptionMatchesNonNilFieldsOrClosure(
}
public class NMBObjCRaiseExceptionMatcher: NSObject, NMBMatcher {
// swiftlint:disable identifier_name
internal var _name: String?
internal var _reason: String?
internal var _userInfo: NSDictionary?
internal var _block: ((NSException) -> Void)?
// swiftlint:enable identifier_name
internal init(name: String?, reason: String?, userInfo: NSDictionary?, block: ((NSException) -> Void)?) {
_name = name
@@ -39,7 +39,7 @@ public func && <T>(left: Predicate<T>, right: Predicate<T>) -> Predicate<T> {
return satisfyAllOf(left, right)
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func satisfyAllOfMatcher(_ matchers: [NMBMatcher]) -> NMBPredicate {
return NMBPredicate { actualExpression in
@@ -60,8 +60,12 @@ extension NMBObjCMatcher {
return predicate.satisfies({ try expression.evaluate() }, location: actualExpression.location).toSwift()
} else {
let failureMessage = FailureMessage()
// swiftlint:disable:next line_length
let success = matcher.matches({ try! expression.evaluate() }, failureMessage: failureMessage, location: actualExpression.location)
let success = matcher.matches(
// swiftlint:disable:next force_try
{ try! expression.evaluate() },
failureMessage: failureMessage,
location: actualExpression.location
)
return PredicateResult(bool: success, message: failureMessage.toExpectationMessage())
}
}
@@ -47,7 +47,7 @@ public func || <T>(left: MatcherFunc<T>, right: MatcherFunc<T>) -> Predicate<T>
return satisfyAnyOf(left, right)
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func satisfyAnyOfMatcher(_ matchers: [NMBMatcher]) -> NMBPredicate {
return NMBPredicate { actualExpression in
@@ -68,8 +68,12 @@ extension NMBObjCMatcher {
return predicate.satisfies({ try expression.evaluate() }, location: actualExpression.location).toSwift()
} else {
let failureMessage = FailureMessage()
// swiftlint:disable:next line_length
let success = matcher.matches({ try! expression.evaluate() }, failureMessage: failureMessage, location: actualExpression.location)
let success = matcher.matches(
// swiftlint:disable:next force_try
{ try! expression.evaluate() },
failureMessage: failureMessage,
location: actualExpression.location
)
return PredicateResult(bool: success, message: failureMessage.toExpectationMessage())
}
}
@@ -2,7 +2,7 @@ import Foundation
public func throwAssertion() -> Predicate<Void> {
return Predicate { actualExpression in
#if arch(x86_64) && (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
#if arch(x86_64) && canImport(Darwin) && !SWIFT_PACKAGE
let message = ExpectationMessage.expectedTo("throw an assertion")
var actualError: Error?
@@ -44,9 +44,8 @@ public func throwAssertion() -> Predicate<Void> {
" conditional statement")
#else
fatalError("The throwAssertion Nimble matcher can only run on x86_64 platforms with " +
"Objective-C (e.g. Mac, iPhone 5s or later simulators). You can silence this error " +
"by placing the test case inside an #if arch(x86_64) or (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) conditional statement")
// swiftlint:disable:previous line_length
"Objective-C (e.g. macOS, iPhone 5s or later simulators). You can silence this error " +
"by placing the test case inside an #if arch(x86_64) or canImport(Darwin) conditional statement")
#endif
}
}
+13 -19
View File
@@ -2,7 +2,7 @@ import CoreFoundation
import Dispatch
import Foundation
#if !(os(macOS) || os(iOS) || os(tvOS) || os(watchOS))
#if canImport(CDispatch)
import CDispatch
#endif
@@ -32,7 +32,7 @@ internal class AssertionWaitLock: WaitLock {
func acquireWaitingLock(_ fnName: String, file: FileString, line: UInt) {
let info = WaitingInfo(name: fnName, file: file, lineNumber: line)
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
let isMainThread = Thread.isMainThread
#else
let isMainThread = _CFIsMainThread()
@@ -45,10 +45,15 @@ internal class AssertionWaitLock: WaitLock {
nimblePrecondition(
currentWaiter == nil,
"InvalidNimbleAPIUsage",
"Nested async expectations are not allowed to avoid creating flaky tests.\n\n" +
"The call to\n\t\(info)\n" +
"triggered this exception because\n\t\(currentWaiter!)\n" +
"is currently managing the main run loop."
"""
Nested async expectations are not allowed to avoid creating flaky tests.
The call to
\t\(info)
triggered this exception because
\t\(currentWaiter!)
is currently managing the main run loop.
"""
)
currentWaiter = info
}
@@ -180,25 +185,18 @@ internal class AwaitPromiseBuilder<T> {
// checked.
//
// In addition, stopping the run loop is used to halt code executed on the main run loop.
#if swift(>=4.0)
trigger.timeoutSource.schedule(
deadline: DispatchTime.now() + timeoutInterval,
repeating: .never,
leeway: timeoutLeeway
)
#else
trigger.timeoutSource.scheduleOneshot(
deadline: DispatchTime.now() + timeoutInterval,
leeway: timeoutLeeway
)
#endif
trigger.timeoutSource.setEventHandler {
guard self.promise.asyncResult.isIncomplete() else { return }
let timedOutSem = DispatchSemaphore(value: 0)
let semTimedOutOrBlocked = DispatchSemaphore(value: 0)
semTimedOutOrBlocked.signal()
let runLoop = CFRunLoopGetMain()
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
let runLoopMode = CFRunLoopMode.defaultMode.rawValue
#else
let runLoopMode = kCFRunLoopDefaultMode
@@ -263,7 +261,7 @@ internal class AwaitPromiseBuilder<T> {
self.trigger.timeoutSource.resume()
while self.promise.asyncResult.isIncomplete() {
// Stopping the run loop does not work unless we run only 1 mode
#if swift(>=4.2)
#if (swift(>=4.2) && canImport(Darwin)) || compiler(>=5.0)
_ = RunLoop.current.run(mode: .default, before: .distantFuture)
#else
_ = RunLoop.current.run(mode: .defaultRunLoopMode, before: .distantFuture)
@@ -333,11 +331,7 @@ internal class Awaiter {
let asyncSource = createTimerSource(asyncQueue)
let trigger = AwaitTrigger(timeoutSource: timeoutSource, actionSource: asyncSource) {
let interval = DispatchTimeInterval.nanoseconds(Int(pollInterval * TimeInterval(NSEC_PER_SEC)))
#if swift(>=4.0)
asyncSource.schedule(deadline: .now(), repeating: interval, leeway: pollLeeway)
#else
asyncSource.scheduleRepeating(deadline: .now(), interval: interval, leeway: pollLeeway)
#endif
asyncSource.setEventHandler {
do {
if let result = try closure() {
+4 -2
View File
@@ -1,12 +1,14 @@
import Foundation
#if !swift(>=4.2)
extension Sequence {
internal func all(_ fn: (Iterator.Element) -> Bool) -> Bool {
internal func allSatisfy(_ predicate: (Element) throws -> Bool) rethrows -> Bool {
for item in self {
if !fn(item) {
if try !predicate(item) {
return false
}
}
return true
}
}
#endif
+3 -2
View File
@@ -2,7 +2,7 @@ import Foundation
internal func identityAsString(_ value: Any?) -> String {
let anyObject: AnyObject?
#if os(Linux)
#if os(Linux) && !swift(>=4.1.50)
anyObject = value as? AnyObject
#else
anyObject = value as AnyObject?
@@ -122,6 +122,7 @@ extension String: TestOutputStringConvertible {
extension Data: TestOutputStringConvertible {
public var testDescription: String {
#if os(Linux)
// swiftlint:disable:next todo
// FIXME: Swift on Linux triggers a segfault when calling NSData's hash() (last checked on 03-11-16)
return "Data<length=\(count)>"
#else
@@ -158,7 +159,7 @@ public func stringify<T>(_ value: T?) -> String {
return String(describing: value)
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
@objc public class NMBStringer: NSObject {
@objc public class func stringify(_ obj: Any?) -> String {
return Nimble.stringify(obj)
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0930"
LastUpgradeVersion = "1010"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
+3 -1
View File
@@ -4,6 +4,7 @@
[![CocoaPods](https://img.shields.io/cocoapods/v/Quick.svg)](https://cocoapods.org/pods/Quick)
[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![Platforms](https://img.shields.io/cocoapods/p/Quick.svg)](https://cocoapods.org/pods/Quick)
[![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com)
Quick is a behavior-driven development framework for Swift and Objective-C.
Inspired by [RSpec](https://github.com/rspec/rspec), [Specta](https://github.com/specta/specta), and [Ginkgo](https://github.com/onsi/ginkgo).
@@ -44,7 +45,8 @@ Certain versions of Quick and Nimble only support certain versions of Swift. Dep
|Swift version |Quick version |Nimble version |
|:--------------------|:---------------|:--------------|
|Swift 3 |v1.0.0 or later |v5.0.0 or later|
|Swift 4.2 |v1.3.2 or later |v7.3.2 or later|
|Swift 3 / Swift 4 |v1.0.0 or later |v5.0.0 or later|
|Swift 2.2 / Swift 2.3|v0.9.3 |v4.1.0 |
## Documentation
+1 -1
View File
@@ -4,7 +4,7 @@
open class Behavior<Context> {
open static var name: String { return String(describing: self) }
public static var name: String { return String(describing: self) }
/**
override this method in your behavior to define a set of reusable examples.
+3 -9
View File
@@ -1,14 +1,8 @@
import Foundation
// `#if swift(>=3.2) && (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE`
// does not work as expected.
#if swift(>=3.2)
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
@objcMembers
public class _CallsiteBase: NSObject {}
#else
public class _CallsiteBase: NSObject {}
#endif
#if canImport(Darwin) && !SWIFT_PACKAGE
@objcMembers
public class _CallsiteBase: NSObject {}
#else
public class _CallsiteBase: NSObject {}
#endif
@@ -72,7 +72,7 @@ final public class Configuration: NSObject {
provided with metadata on the example that the closure is being run
prior to.
*/
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
@objc(beforeEachWithMetadata:)
public func beforeEach(_ closure: @escaping BeforeExampleWithMetadataClosure) {
exampleHooks.appendBefore(closure)
@@ -109,7 +109,7 @@ final public class Configuration: NSObject {
is provided with metadata on the example that the closure is being
run after.
*/
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
@objc(afterEachWithMetadata:)
public func afterEach(_ closure: @escaping AfterExampleWithMetadataClosure) {
exampleHooks.appendAfter(closure)
+3 -3
View File
@@ -56,7 +56,7 @@ extension World {
currentExampleGroup.hooks.appendBefore(closure)
}
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
#if canImport(Darwin) && !SWIFT_PACKAGE
@objc(beforeEachWithMetadata:)
internal func beforeEach(closure: @escaping BeforeExampleWithMetadataClosure) {
currentExampleGroup.hooks.appendBefore(closure)
@@ -74,7 +74,7 @@ extension World {
currentExampleGroup.hooks.appendAfter(closure)
}
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
#if canImport(Darwin) && !SWIFT_PACKAGE
@objc(afterEachWithMetadata:)
internal func afterEach(closure: @escaping AfterExampleWithMetadataClosure) {
currentExampleGroup.hooks.appendAfter(closure)
@@ -172,7 +172,7 @@ extension World {
self.itBehavesLike(behavior, context: context, flags: pendingFlags, file: file, line: line)
}
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
#if canImport(Darwin) && !SWIFT_PACKAGE
@objc(itWithDescription:flags:file:line:closure:)
internal func objc_it(_ description: String, flags: FilterFlags, file: String, line: UInt, closure: @escaping () -> Void) {
it(description, flags: flags, file: file, line: line, closure: closure)
+1 -1
View File
@@ -1,7 +1,7 @@
import Foundation
internal func raiseError(_ message: String) -> Never {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
NSException(name: .internalInconsistencyException, reason: message, userInfo: nil).raise()
#endif
+3 -9
View File
@@ -3,15 +3,9 @@ import Foundation
private var numberOfExamplesRun = 0
private var numberOfIncludedExamples = 0
// `#if swift(>=3.2) && (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE`
// does not work as expected.
#if swift(>=3.2)
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
@objcMembers
public class _ExampleBase: NSObject {}
#else
public class _ExampleBase: NSObject {}
#endif
#if canImport(Darwin) && !SWIFT_PACKAGE
@objcMembers
public class _ExampleBase: NSObject {}
#else
public class _ExampleBase: NSObject {}
#endif
+3 -9
View File
@@ -1,14 +1,8 @@
import Foundation
// `#if swift(>=3.2) && (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE`
// does not work as expected.
#if swift(>=3.2)
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
@objcMembers
public class _ExampleMetadataBase: NSObject {}
#else
public class _ExampleMetadataBase: NSObject {}
#endif
#if canImport(Darwin) && !SWIFT_PACKAGE
@objcMembers
public class _ExampleMetadataBase: NSObject {}
#else
public class _ExampleMetadataBase: NSObject {}
#endif
+3 -9
View File
@@ -1,14 +1,8 @@
import Foundation
// `#if swift(>=3.2) && (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE`
// does not work as expected.
#if swift(>=3.2)
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
@objcMembers
public class _FilterBase: NSObject {}
#else
public class _FilterBase: NSObject {}
#endif
#if canImport(Darwin) && !SWIFT_PACKAGE
@objcMembers
public class _FilterBase: NSObject {}
#else
public class _FilterBase: NSObject {}
#endif
@@ -1,4 +1,4 @@
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
#if canImport(Darwin)
import Foundation
@@ -1,4 +1,4 @@
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
#if canImport(Darwin)
import Foundation
extension NSString {
@@ -21,7 +21,7 @@ extension NSString {
return invalidCharacters
}()
/// This API is not meant to be used outside Quick, so will be unavaialbe in
/// This API is not meant to be used outside Quick, so will be unavailable in
/// a next major version.
@objc(qck_c99ExtendedIdentifier)
public var c99ExtendedIdentifier: String {
@@ -1,4 +1,4 @@
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
#if canImport(Darwin)
import Foundation
/**
+1 -1
View File
@@ -1,4 +1,4 @@
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
#if canImport(Darwin)
import XCTest
+5 -11
View File
@@ -12,15 +12,9 @@ public typealias SharedExampleContext = () -> [String: Any]
*/
public typealias SharedExampleClosure = (@escaping SharedExampleContext) -> Void
// `#if swift(>=3.2) && (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE`
// does not work as expected.
#if swift(>=3.2)
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
@objcMembers
internal class _WorldBase: NSObject {}
#else
internal class _WorldBase: NSObject {}
#endif
#if canImport(Darwin) && !SWIFT_PACKAGE
@objcMembers
internal class _WorldBase: NSObject {}
#else
internal class _WorldBase: NSObject {}
#endif
@@ -57,7 +51,7 @@ final internal class World: _WorldBase {
within this test suite. This is only true within the context of Quick
functional tests.
*/
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
// Convention of generating Objective-C selector has been changed on Swift 3
@objc(isRunningAdditionalSuites)
internal var isRunningAdditionalSuites = false
@@ -158,7 +152,7 @@ final internal class World: _WorldBase {
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
@objc(examplesForSpecClass:)
internal func objc_examples(_ specClass: AnyClass) -> [Example] {
return examples(specClass)
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>7.3.1</string>
<string>8.0.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.3.2</string>
<string>2.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.3.3</string>
<string>0.7.2</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
+32 -12
View File
@@ -24,6 +24,10 @@
074A6483205C155E0083D868 /* AVPlayerTimeObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074A6482205C155E0083D868 /* AVPlayerTimeObserverTests.swift */; };
074A6485205C29920083D868 /* AVPlayerItemNotificationObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074A6484205C29920083D868 /* AVPlayerItemNotificationObserverTests.swift */; };
074A6487205E59B60083D868 /* AVPlayerWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074A6486205E59B60083D868 /* AVPlayerWrapperTests.swift */; };
074B0D67222C1EC7001A45A9 /* NowPlayingInfoControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074B0D66222C1EC7001A45A9 /* NowPlayingInfoControllerTests.swift */; };
074B0D6B222C247B001A45A9 /* NowPlayingInfoCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074B0D6A222C247B001A45A9 /* NowPlayingInfoCenter.swift */; };
074B0D6D222C24DE001A45A9 /* NowPlayingInfoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074B0D6C222C24DE001A45A9 /* NowPlayingInfoController.swift */; };
076DFC5F22345EAF00A8D163 /* AudioPlayerEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 076DFC5E22345EAF00A8D163 /* AudioPlayerEventTests.swift */; };
07732651205EACA300C4D1CD /* WAV-MP3.wav in Resources */ = {isa = PBXBuildFile; fileRef = 07732650205EACA300C4D1CD /* WAV-MP3.wav */; };
07732653205EB1B500C4D1CD /* nasa_throttle_up.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 07732652205EB1B500C4D1CD /* nasa_throttle_up.mp3 */; };
07732654205ECA8B00C4D1CD /* WAV-MP3.wav in Resources */ = {isa = PBXBuildFile; fileRef = 07732650205EACA300C4D1CD /* WAV-MP3.wav */; };
@@ -32,6 +36,7 @@
07756B69218A4E870023935E /* AudioSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07756B68218A4E870023935E /* AudioSession.swift */; };
078C908F210D263200555E80 /* AVPlayerItemObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 078C908D210D25F700555E80 /* AVPlayerItemObserverTests.swift */; };
07DBB1E1212C17E600BB4278 /* QueuedAudioPlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07DBB1E0212C17E600BB4278 /* QueuedAudioPlayerTests.swift */; };
07EB8EE2222869B2000197DE /* NowPlayingInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07EB8EE022286980000197DE /* NowPlayingInfoTests.swift */; };
607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; };
607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; };
607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; };
@@ -65,12 +70,17 @@
074A6482205C155E0083D868 /* AVPlayerTimeObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerTimeObserverTests.swift; sourceTree = "<group>"; };
074A6484205C29920083D868 /* AVPlayerItemNotificationObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerItemNotificationObserverTests.swift; sourceTree = "<group>"; };
074A6486205E59B60083D868 /* AVPlayerWrapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerWrapperTests.swift; sourceTree = "<group>"; };
074B0D66222C1EC7001A45A9 /* NowPlayingInfoControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingInfoControllerTests.swift; sourceTree = "<group>"; };
074B0D6A222C247B001A45A9 /* NowPlayingInfoCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingInfoCenter.swift; sourceTree = "<group>"; };
074B0D6C222C24DE001A45A9 /* NowPlayingInfoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingInfoController.swift; sourceTree = "<group>"; };
076DFC5E22345EAF00A8D163 /* AudioPlayerEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerEventTests.swift; sourceTree = "<group>"; };
07732650205EACA300C4D1CD /* WAV-MP3.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = "WAV-MP3.wav"; sourceTree = "<group>"; };
07732652205EB1B500C4D1CD /* nasa_throttle_up.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = nasa_throttle_up.mp3; sourceTree = "<group>"; };
0775575820668B020002C6A1 /* QueueManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueManagerTests.swift; sourceTree = "<group>"; };
07756B68218A4E870023935E /* AudioSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioSession.swift; sourceTree = "<group>"; };
078C908D210D25F700555E80 /* AVPlayerItemObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerItemObserverTests.swift; sourceTree = "<group>"; };
07DBB1E0212C17E600BB4278 /* QueuedAudioPlayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueuedAudioPlayerTests.swift; sourceTree = "<group>"; };
07EB8EE022286980000197DE /* NowPlayingInfoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingInfoTests.swift; sourceTree = "<group>"; };
521F3AEC1228A2FA2637355F /* Pods-SwiftAudio_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftAudio_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftAudio_Tests/Pods-SwiftAudio_Tests.debug.xcconfig"; sourceTree = "<group>"; };
607FACD01AFB9204008FA782 /* SwiftAudio_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftAudio_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -128,6 +138,8 @@
isa = PBXGroup;
children = (
07756B68218A4E870023935E /* AudioSession.swift */,
074B0D6A222C247B001A45A9 /* NowPlayingInfoCenter.swift */,
074B0D6C222C24DE001A45A9 /* NowPlayingInfoController.swift */,
);
path = Mocks;
sourceTree = "<group>";
@@ -193,6 +205,9 @@
078C908D210D25F700555E80 /* AVPlayerItemObserverTests.swift */,
0708ED6B2116DA4B00EB29BD /* AudioSessionControllerTests.swift */,
07DBB1E0212C17E600BB4278 /* QueuedAudioPlayerTests.swift */,
07EB8EE022286980000197DE /* NowPlayingInfoTests.swift */,
074B0D66222C1EC7001A45A9 /* NowPlayingInfoControllerTests.swift */,
076DFC5E22345EAF00A8D163 /* AudioPlayerEventTests.swift */,
0708ED712116E91300EB29BD /* Source */,
607FACE91AFB9204008FA782 /* Supporting Files */,
);
@@ -286,13 +301,13 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0830;
LastUpgradeCheck = 0830;
LastUpgradeCheck = 1010;
ORGANIZATIONNAME = CocoaPods;
TargetAttributes = {
607FACCF1AFB9204008FA782 = {
CreatedOnToolsVersion = 6.3.1;
DevelopmentTeam = HPNZWPB9JK;
LastSwiftMigration = 0900;
LastSwiftMigration = 1020;
SystemCapabilities = {
com.apple.BackgroundModes = {
enabled = 1;
@@ -302,14 +317,14 @@
607FACE41AFB9204008FA782 = {
CreatedOnToolsVersion = 6.3.1;
DevelopmentTeam = HPNZWPB9JK;
LastSwiftMigration = 0900;
LastSwiftMigration = 1020;
TestTargetID = 607FACCF1AFB9204008FA782;
};
};
};
buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "SwiftAudio" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
@@ -452,16 +467,21 @@
buildActionMask = 2147483647;
files = (
07756B69218A4E870023935E /* AudioSession.swift in Sources */,
074B0D67222C1EC7001A45A9 /* NowPlayingInfoControllerTests.swift in Sources */,
0708ED702116E89900EB29BD /* Source.swift in Sources */,
0708ED742116EE0100EB29BD /* AudioPlayerTests.swift in Sources */,
0775575920668B020002C6A1 /* QueueManagerTests.swift in Sources */,
074A6483205C155E0083D868 /* AVPlayerTimeObserverTests.swift in Sources */,
078C908F210D263200555E80 /* AVPlayerItemObserverTests.swift in Sources */,
0708ED6C2116DA4C00EB29BD /* AudioSessionControllerTests.swift in Sources */,
074B0D6B222C247B001A45A9 /* NowPlayingInfoCenter.swift in Sources */,
07DBB1E1212C17E600BB4278 /* QueuedAudioPlayerTests.swift in Sources */,
076DFC5F22345EAF00A8D163 /* AudioPlayerEventTests.swift in Sources */,
074B0D6D222C24DE001A45A9 /* NowPlayingInfoController.swift in Sources */,
074A6485205C29920083D868 /* AVPlayerItemNotificationObserverTests.swift in Sources */,
607FACEC1AFB9204008FA782 /* AVPlayerObserverTests.swift in Sources */,
074A6487205E59B60083D868 /* AVPlayerWrapperTests.swift in Sources */,
07EB8EE2222869B2000197DE /* NowPlayingInfoTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -507,12 +527,14 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
@@ -560,12 +582,14 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
@@ -606,8 +630,7 @@
MODULE_NAME = ExampleApp;
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
};
name = Debug;
@@ -624,8 +647,7 @@
MODULE_NAME = ExampleApp;
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
};
name = Release;
@@ -647,8 +669,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftAudio_Example.app/SwiftAudio_Example";
};
name = Debug;
@@ -666,8 +687,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftAudio_Example.app/SwiftAudio_Example";
};
name = Release;
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0900"
LastUpgradeVersion = "1010"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
+1 -1
View File
@@ -14,7 +14,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
application.beginReceivingRemoteControlEvents()
+4 -4
View File
@@ -17,10 +17,10 @@ class AudioController {
let audioSessionController = AudioSessionController.shared
let sources: [AudioItem] = [
DefaultAudioItem(audioUrl: "https://p.scdn.co/mp3-preview/67b51d90ffddd6bb3f095059997021b589845f81?cid=d8a5ed958d274c2e8ee717e6a4b0971d", artist: "Bon Iver", title: "33 \"GOD\"", albumTitle: "22, A Million", sourceType: .stream, pitchAlgorithmType: .lowQualityZeroLatency, artwork: #imageLiteral(resourceName: "22AMI")),
DefaultAudioItem(audioUrl: "https://p.scdn.co/mp3-preview/081447adc23dad4f79ba4f1082615d1c56edf5e1?cid=d8a5ed958d274c2e8ee717e6a4b0971d", artist: "Bon Iver", title: "8 (circle)", albumTitle: "22, A Million", sourceType: .stream, pitchAlgorithmType: .lowQualityZeroLatency, artwork: #imageLiteral(resourceName: "22AMI")),
DefaultAudioItem(audioUrl: "https://p.scdn.co/mp3-preview/6f9999d909b017eabef97234dd7a206355720d9d?cid=d8a5ed958d274c2e8ee717e6a4b0971d", artist: "Bon Iver", title: "715 - CRΣΣKS", albumTitle: "22, A Million", sourceType: .stream, pitchAlgorithmType: .lowQualityZeroLatency, artwork: #imageLiteral(resourceName: "22AMI")),
DefaultAudioItem(audioUrl: "https://p.scdn.co/mp3-preview/bf9bdd403c67fdbe06a582e7b292487c8cfd1f7e?cid=d8a5ed958d274c2e8ee717e6a4b0971d", artist: "Bon Iver", title: "____45_____", albumTitle: "22, A Million", sourceType: .stream, pitchAlgorithmType: .lowQualityZeroLatency, artwork: #imageLiteral(resourceName: "22AMI"))
DefaultAudioItem(audioUrl: "https://p.scdn.co/mp3-preview/67b51d90ffddd6bb3f095059997021b589845f81?cid=d8a5ed958d274c2e8ee717e6a4b0971d", artist: "Bon Iver", title: "33 \"GOD\"", albumTitle: "22, A Million", sourceType: .stream, artwork: #imageLiteral(resourceName: "22AMI")),
DefaultAudioItem(audioUrl: "https://p.scdn.co/mp3-preview/081447adc23dad4f79ba4f1082615d1c56edf5e1?cid=d8a5ed958d274c2e8ee717e6a4b0971d", artist: "Bon Iver", title: "8 (circle)", albumTitle: "22, A Million", sourceType: .stream, artwork: #imageLiteral(resourceName: "22AMI")),
DefaultAudioItem(audioUrl: "https://p.scdn.co/mp3-preview/6f9999d909b017eabef97234dd7a206355720d9d?cid=d8a5ed958d274c2e8ee717e6a4b0971d", artist: "Bon Iver", title: "715 - CRΣΣKS", albumTitle: "22, A Million", sourceType: .stream, artwork: #imageLiteral(resourceName: "22AMI")),
DefaultAudioItem(audioUrl: "https://p.scdn.co/mp3-preview/bf9bdd403c67fdbe06a582e7b292487c8cfd1f7e?cid=d8a5ed958d274c2e8ee717e6a4b0971d", artist: "Bon Iver", title: "____45_____", albumTitle: "22, A Million", sourceType: .stream, artwork: #imageLiteral(resourceName: "22AMI"))
]
init() {
+18 -4
View File
@@ -1,12 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="vXZ-lx-hvc">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="vXZ-lx-hvc">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@@ -63,7 +62,7 @@
</connections>
</button>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="FCd-3e-22D">
<rect key="frame" x="67" y="84" width="240" height="240"/>
<rect key="frame" x="67.5" y="84" width="240" height="240"/>
<constraints>
<constraint firstAttribute="width" constant="240" id="5Sj-BZ-sg4"/>
<constraint firstAttribute="height" constant="240" id="Hij-Yw-6Lg"/>
@@ -105,24 +104,37 @@
<color key="textColor" red="0.99999600649999998" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="white" translatesAutoresizingMaskIntoConstraints="NO" id="1ML-yD-9Rf">
<rect key="frame" x="177.5" y="587" width="20" height="20"/>
</activityIndicatorView>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="ErrorText" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="iCe-6A-2My">
<rect key="frame" x="158.5" y="588.5" width="58.5" height="17"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="1" green="0.14913141730000001" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="0.12984204290000001" green="0.12984612579999999" blue="0.12984395030000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="tintColor" red="1" green="0.83234566450000003" blue="0.47320586440000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="T7Y-1Q-7UU" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leadingMargin" id="0eh-sL-186"/>
<constraint firstItem="iCe-6A-2My" firstAttribute="centerY" secondItem="1ML-yD-9Rf" secondAttribute="centerY" id="4Fp-kE-AAg"/>
<constraint firstItem="l9B-hM-Ajc" firstAttribute="trailing" secondItem="kh9-bI-dsS" secondAttribute="trailingMargin" id="54L-0h-0ba"/>
<constraint firstItem="l9B-hM-Ajc" firstAttribute="top" secondItem="jyV-Pf-zRb" secondAttribute="bottom" id="9Uh-K9-988"/>
<constraint firstItem="RVb-HZ-QCX" firstAttribute="trailing" secondItem="kh9-bI-dsS" secondAttribute="trailingMargin" id="BhV-UD-qhh"/>
<constraint firstItem="iCe-6A-2My" firstAttribute="centerX" secondItem="kh9-bI-dsS" secondAttribute="centerX" id="Dhm-Bn-wZH"/>
<constraint firstItem="FCd-3e-22D" firstAttribute="centerX" secondItem="kh9-bI-dsS" secondAttribute="centerX" id="GhI-f1-DkR"/>
<constraint firstItem="T7Y-1Q-7UU" firstAttribute="trailing" secondItem="kh9-bI-dsS" secondAttribute="trailingMargin" id="HoH-i0-yof"/>
<constraint firstItem="RWN-If-dGG" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leadingMargin" id="Nw7-WM-LFd"/>
<constraint firstItem="RX3-VR-CL6" firstAttribute="centerX" secondItem="kh9-bI-dsS" secondAttribute="centerX" id="O0h-NL-iXW"/>
<constraint firstItem="1ML-yD-9Rf" firstAttribute="top" secondItem="EOo-zV-6l2" secondAttribute="bottom" constant="20" id="Uop-aD-I5b"/>
<constraint firstItem="dfk-yr-rwm" firstAttribute="top" secondItem="FCd-3e-22D" secondAttribute="bottom" constant="30" id="W4w-6K-AW8"/>
<constraint firstItem="RWN-If-dGG" firstAttribute="top" secondItem="T7Y-1Q-7UU" secondAttribute="bottom" constant="25" id="XgV-XL-QCL"/>
<constraint firstItem="dfk-yr-rwm" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leadingMargin" id="YUE-uf-Rp1"/>
<constraint firstItem="RVb-HZ-QCX" firstAttribute="top" secondItem="RWN-If-dGG" secondAttribute="bottom" constant="8" id="ZkD-u2-Zbr"/>
<constraint firstItem="T7Y-1Q-7UU" firstAttribute="top" secondItem="dfk-yr-rwm" secondAttribute="bottom" constant="4" id="baR-zV-tgo"/>
<constraint firstItem="RWN-If-dGG" firstAttribute="trailing" secondItem="kh9-bI-dsS" secondAttribute="trailingMargin" id="eNt-u9-qot"/>
<constraint firstItem="1ML-yD-9Rf" firstAttribute="centerX" secondItem="kh9-bI-dsS" secondAttribute="centerX" id="fdl-RK-Hq8"/>
<constraint firstItem="RX3-VR-CL6" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leadingMargin" constant="16" id="hEd-b2-Ggo"/>
<constraint firstItem="FCd-3e-22D" firstAttribute="top" secondItem="l9B-hM-Ajc" secondAttribute="bottom" constant="30" id="ikz-ZP-jNM"/>
<constraint firstAttribute="trailingMargin" secondItem="RX3-VR-CL6" secondAttribute="trailing" constant="16" id="kSP-Mq-R5P"/>
@@ -135,7 +147,9 @@
<connections>
<outlet property="artistLabel" destination="T7Y-1Q-7UU" id="b5S-lt-PqG"/>
<outlet property="elapsedTimeLabel" destination="3CL-8o-zYW" id="7Wg-7X-Vrd"/>
<outlet property="errorLabel" destination="iCe-6A-2My" id="T4b-0b-wdM"/>
<outlet property="imageView" destination="FCd-3e-22D" id="gKL-za-haV"/>
<outlet property="loadIndicator" destination="1ML-yD-9Rf" id="Xes-Ag-vhg"/>
<outlet property="playButton" destination="EOo-zV-6l2" id="2d1-ad-s1k"/>
<outlet property="remainingTimeLabel" destination="RVb-HZ-QCX" id="8hp-CK-XjF"/>
<outlet property="slider" destination="RWN-If-dGG" id="Yxw-Gf-bR3"/>
+1 -1
View File
@@ -46,7 +46,7 @@ extension QueueViewController: UITableViewDataSource, UITableViewDelegate {
case 0:
return 1
case 1:
return controller.player.nextItems.count ?? 0
return controller.player.nextItems.count
default:
return 0
}
+91 -48
View File
@@ -21,20 +21,37 @@ class ViewController: UIViewController {
@IBOutlet weak var elapsedTimeLabel: UILabel!
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var artistLabel: UILabel!
@IBOutlet weak var loadIndicator: UIActivityIndicatorView!
@IBOutlet weak var errorLabel: UILabel!
var isScrubbing: Bool = false
let controller = AudioController.shared
private var isScrubbing: Bool = false
private let controller = AudioController.shared
private var lastLoadFailed: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
controller.player.delegate = self
controller.player.event.stateChange.addListener(self, handleAudioPlayerStateChange)
controller.player.event.secondElapse.addListener(self, handleAudioPlayerSecondElapsed)
controller.player.event.seek.addListener(self, handleAudioPlayerDidSeek)
controller.player.event.updateDuration.addListener(self, handleAudioPlayerUpdateDuration)
controller.player.event.didRecreateAVPlayer.addListener(self, handleAVPlayerRecreated)
controller.player.event.fail.addListener(self, handlePlayerFailure)
updateMetaData()
handleAudioPlayerStateChange(data: controller.player.playerState)
}
@IBAction func togglePlay(_ sender: Any) {
if (!controller.audioSessionController.audioSessionIsActive) {
if !controller.audioSessionController.audioSessionIsActive {
try? controller.audioSessionController.activateSession()
}
try? controller.player.togglePlaying()
if lastLoadFailed, let item = controller.player.currentItem {
lastLoadFailed = false
errorLabel.isHidden = true
try? controller.player.load(item: item, playWhenReady: true)
}
else {
controller.player.togglePlaying()
}
}
@IBAction func previous(_ sender: Any) {
@@ -50,7 +67,7 @@ class ViewController: UIViewController {
}
@IBAction func scrubbing(_ sender: UISlider) {
try? controller.player.seek(to: Double(slider.value))
controller.player.seek(to: Double(slider.value))
}
@IBAction func scrubbingValueChanged(_ sender: UISlider) {
@@ -58,63 +75,89 @@ class ViewController: UIViewController {
elapsedTimeLabel.text = value.secondsToString()
remainingTimeLabel.text = (controller.player.duration - value).secondsToString()
}
}
extension ViewController: AudioPlayerDelegate {
func audioPlayer(itemPlaybackEndedWithReason reason: PlaybackEndedReason) {
func updateTimeValues() {
self.slider.maximumValue = Float(self.controller.player.duration)
self.slider.setValue(Float(self.controller.player.currentTime), animated: true)
self.elapsedTimeLabel.text = self.controller.player.currentTime.secondsToString()
self.remainingTimeLabel.text = (self.controller.player.duration - self.controller.player.currentTime).secondsToString()
}
func updateMetaData() {
if let item = controller.player.currentItem {
titleLabel.text = item.getTitle()
artistLabel.text = item.getArtist()
item.getArtwork({ (image) in
self.imageView.image = image
})
}
}
func audioPlayer(playerDidChangeState state: AVPlayerWrapperState) {
func setPlayButtonState(forAudioPlayerState state: AudioPlayerState) {
playButton.setTitle(state == .playing ? "Pause" : "Play", for: .normal)
switch state {
case .ready:
if let item = controller.player.currentItem {
titleLabel.text = item.getTitle()
artistLabel.text = item.getArtist()
item.getArtwork({ (image) in
self.imageView.image = image
})
}
func setErrorMessage(_ message: String) {
self.loadIndicator.stopAnimating()
errorLabel.isHidden = false
errorLabel.text = message
}
// MARK: - AudioPlayer Event Handlers
func handleAudioPlayerStateChange(data: AudioPlayer.StateChangeEventData) {
print(data)
DispatchQueue.main.async {
self.setPlayButtonState(forAudioPlayerState: data)
switch data {
case .loading:
self.loadIndicator.startAnimating()
self.updateMetaData()
self.updateTimeValues()
case .buffering:
self.loadIndicator.startAnimating()
case .ready:
self.loadIndicator.stopAnimating()
self.updateMetaData()
self.updateTimeValues()
case .playing, .paused, .idle:
self.loadIndicator.stopAnimating()
self.updateTimeValues()
}
slider.maximumValue = Float(controller.player.duration)
slider.setValue(Float(controller.player.currentTime), animated: true)
elapsedTimeLabel.text = controller.player.currentTime.secondsToString()
remainingTimeLabel.text = (controller.player.duration - controller.player.currentTime).secondsToString()
case .loading, .playing, .paused, .idle:
slider.maximumValue = Float(controller.player.duration)
slider.setValue(Float(controller.player.currentTime), animated: true)
}
}
func audioPlayer(secondsElapsed seconds: Double) {
func handleAudioPlayerSecondElapsed(data: AudioPlayer.SecondElapseEventData) {
if !isScrubbing {
slider.setValue(Float(seconds), animated: false)
elapsedTimeLabel.text = controller.player.currentTime.secondsToString()
remainingTimeLabel.text = (controller.player.duration - controller.player.currentTime).secondsToString()
DispatchQueue.main.async {
self.updateTimeValues()
}
}
}
func audioPlayer(failedWithError error: Error?) {
}
func audioPlayer(seekTo seconds: Int, didFinish: Bool) {
func handleAudioPlayerDidSeek(data: AudioPlayer.SeekEventData) {
isScrubbing = false
}
func audioPlayer(didUpdateDuration duration: Double) {
slider.maximumValue = Float(controller.player.duration)
slider.setValue(Float(controller.player.currentTime), animated: true)
elapsedTimeLabel.text = controller.player.currentTime.secondsToString()
remainingTimeLabel.text = (controller.player.duration - controller.player.currentTime).secondsToString()
func handleAudioPlayerUpdateDuration(data: AudioPlayer.UpdateDurationEventData) {
DispatchQueue.main.async {
self.updateTimeValues()
}
}
func handleAVPlayerRecreated() {
try? controller.audioSessionController.set(category: .playback)
}
func handlePlayerFailure(data: AudioPlayer.FailEventData) {
if let error = data as NSError? {
if error.code == -1009 {
lastLoadFailed = true
DispatchQueue.main.async {
self.setErrorMessage("Network disconnected. Please try again...")
}
}
}
}
}
+22 -5
View File
@@ -7,8 +7,8 @@ import AVFoundation
class AVPlayerObserverTests: QuickSpec, AVPlayerObserverDelegate {
var status: AVPlayerStatus?
var timeControlStatus: AVPlayerTimeControlStatus?
var status: AVPlayer.Status?
var timeControlStatus: AVPlayer.TimeControlStatus?
override func spec() {
@@ -19,10 +19,16 @@ class AVPlayerObserverTests: QuickSpec, AVPlayerObserverDelegate {
beforeEach {
player = AVPlayer()
observer = AVPlayerObserver(player: player)
player.volume = 0.0
observer = AVPlayerObserver()
observer.player = player
observer.delegate = self
}
it("should not be observing", closure: {
expect(observer.isObserving).to(beFalse())
})
context("when observing has started", {
beforeEach {
observer.startObserving()
@@ -53,16 +59,27 @@ class AVPlayerObserverTests: QuickSpec, AVPlayerObserverDelegate {
expect(observer.isObserving).toEventually(beTrue())
})
})
context("when stopping observing", closure: {
beforeEach {
observer.stopObserving()
}
it("should not be observing", closure: {
expect(observer.isObserving).to(beFalse())
})
})
})
}
}
func player(statusDidChange status: AVPlayerStatus) {
func player(statusDidChange status: AVPlayer.Status) {
self.status = status
}
func player(didChangeTimeControlStatus status: AVPlayerTimeControlStatus) {
func player(didChangeTimeControlStatus status: AVPlayer.TimeControlStatus) {
self.timeControlStatus = status
}
@@ -17,7 +17,8 @@ class AVPlayerTimeObserverTests: QuickSpec {
player = AVPlayer()
player.automaticallyWaitsToMinimizeStalling = false
player.volume = 0
observer = AVPlayerTimeObserver(player: player, periodicObserverTimeInterval: TimeEventFrequency.everyQuarterSecond.getTime())
observer = AVPlayerTimeObserver(periodicObserverTimeInterval: TimeEventFrequency.everyQuarterSecond.getTime())
observer.player = player
}
context("has started boundary time observing", {
+181 -213
View File
@@ -1,225 +1,192 @@
import Quick
import Nimble
import AVFoundation
import XCTest
@testable import SwiftAudio
class AVPlayerWrapperTests: QuickSpec {
override func spec() {
describe("An AVPlayerWrapper") {
var wrapper: AVPlayerWrapper!
beforeEach {
let player = AVPlayer()
player.automaticallyWaitsToMinimizeStalling = false
player.volume = 0.0
wrapper = AVPlayerWrapper(avPlayer: player)
wrapper.bufferDuration = 0.0001
class AVPlayerWrapperTests: XCTestCase {
var wrapper: AVPlayerWrapper!
var holder: AVPlayerWrapperDelegateHolder!
override func setUp() {
super.setUp()
wrapper = AVPlayerWrapper()
wrapper.volume = 0.0
wrapper.automaticallyWaitsToMinimizeStalling = false
holder = AVPlayerWrapperDelegateHolder()
wrapper.delegate = holder
}
override func tearDown() {
wrapper = nil
holder = nil
super.tearDown()
}
// MARK: - State tests
func test_AVPlayerWrapper__state__should_be_idle() {
XCTAssert(wrapper.state == AVPlayerWrapperState.idle)
}
func test_AVPlayerWrapper__state__when_loading_a_source__should_be_loading() {
wrapper.load(from: Source.url, playWhenReady: false)
XCTAssertEqual(wrapper.state, AVPlayerWrapperState.loading)
}
func test_AVPlayerWrapper__state__when_loading_a_source__should_eventually_be_ready() {
let expectation = XCTestExpectation()
holder.stateUpdate = { state in
if state == .ready {
expectation.fulfill()
}
describe("its state", {
it("should be idle", closure: {
expect(wrapper.state).to(equal(AVPlayerWrapperState.idle))
})
context("when loading a source", {
beforeEach {
wrapper.load(from: URL(fileURLWithPath: Source.path), playWhenReady: false)
}
it("should be loading", closure: {
expect(wrapper.state).to(equal(AVPlayerWrapperState.loading))
})
it("should eventually be ready", closure: {
expect(wrapper.state).toEventually(equal(AVPlayerWrapperState.ready))
})
})
context("when playing with no source", {
beforeEach {
wrapper.play()
}
it("should be idle", closure: {
expect(wrapper.state).to(equal(AVPlayerWrapperState.idle))
})
})
context("when playing a source", {
beforeEach {
wrapper.load(from: URL(fileURLWithPath: Source.path), playWhenReady: true)
}
it("should eventually be playing", closure: {
expect(wrapper.state).toEventually(equal(AVPlayerWrapperState.playing))
})
})
context("when pausing the source", {
let holder = AVPlayerWrapperDelegateHolder()
beforeEach {
wrapper.delegate = holder
holder.stateUpdate = { (state) in
if state == .playing {
wrapper.pause()
}
}
wrapper.load(from: URL(fileURLWithPath: Source.path), playWhenReady: true)
}
it("should eventually be paused", closure: {
expect(wrapper.state).toEventually(equal(AVPlayerWrapperState.paused))
})
})
context("when toggling the source from play", {
let holder = AVPlayerWrapperDelegateHolder()
beforeEach {
wrapper.delegate = holder
holder.stateUpdate = { (state) in
if state == .playing {
wrapper.togglePlaying()
}
}
wrapper.load(from: URL(fileURLWithPath: Source.path), playWhenReady: true)
}
it("should eventually be paused", closure: {
expect(wrapper.state).toEventually(equal(AVPlayerWrapperState.paused))
})
})
context("when stopping the source", {
var holder: AVPlayerWrapperDelegateHolder!
var receivedIdleUpdate: Bool = false
beforeEach {
holder = AVPlayerWrapperDelegateHolder()
wrapper.delegate = holder
holder.stateUpdate = { (state) in
if state == .playing {
wrapper.stop()
}
if state == .idle {
receivedIdleUpdate = true
}
}
wrapper.load(from: URL(fileURLWithPath: Source.path), playWhenReady: true)
}
it("should eventually be 'idle'", closure: {
expect(receivedIdleUpdate).toEventually(beTrue())
})
})
context("when seeking before loading", {
beforeEach {
wrapper.seek(to: 10)
}
it("should be idle", closure: {
expect(wrapper.state).to(equal(AVPlayerWrapperState.idle))
})
})
})
describe("its duration", {
it("should be 0", closure: {
expect(wrapper.duration).to(equal(0))
})
context("when loading source", {
beforeEach {
wrapper.load(from: URL(fileURLWithPath: Source.path), playWhenReady: false)
}
it("should eventually not be 0", closure: {
expect(wrapper.duration).toEventuallyNot(equal(0))
})
})
})
describe("its current time", {
it("should be 0", closure: {
expect(wrapper.currentTime).to(equal(0))
})
context("when seeking to a time", {
var passed = false
let holder = AVPlayerWrapperDelegateHolder()
let seekTime: TimeInterval = 0.5
beforeEach {
wrapper.delegate = holder
holder.seekCompletion = { passed = true }
wrapper.load(from: Source.url, playWhenReady: false)
wrapper.seek(to: seekTime)
}
it("should eventually be equal to the seeked time", closure: {
expect(passed).toEventually(beTrue())
})
})
})
describe("its rate", {
it("should be 0", closure: {
expect(wrapper.rate).to(equal(0.0))
})
context("when playing a source", {
beforeEach {
wrapper.load(from: URL(fileURLWithPath: Source.path), playWhenReady: true)
}
it("should eventually be 1.0", closure: {
expect(wrapper.rate).toEventually(equal(1.0))
})
})
})
describe("its automaticallyWaitsToMinimizeStalling option", {
it("should be false", closure: {
expect(wrapper.automaticallyWaitsToMinimizeStalling).to(beFalse())
})
context("when setting it to true", {
beforeEach {
wrapper.automaticallyWaitsToMinimizeStalling = true
}
it("should be true", closure: {
expect(wrapper.automaticallyWaitsToMinimizeStalling).to(beTrue())
})
})
})
describe("its timeEventFrequency", {
context("when updated", {
beforeEach {
wrapper.timeEventFrequency = .everyHalfSecond
}
it("should update the playerTimeObservers periodicObserverTimeInterval", closure: {
expect(wrapper.playerTimeObserver.periodicObserverTimeInterval).to(equal(TimeEventFrequency.everyHalfSecond.getTime()))
})
})
})
}
wrapper.load(from: Source.url, playWhenReady: false)
wait(for: [expectation], timeout: 20.0)
}
func test_AVPlayerWrapper__state__when_playing_a_source__should_be_playing() {
let expectation = XCTestExpectation()
holder.stateUpdate = { state in
if state == .playing {
expectation.fulfill()
}
}
wrapper.load(from: Source.url, playWhenReady: true)
wait(for: [expectation], timeout: 20.0)
}
func test_AVPlayerWrapper__state__when_pausing_a_source__should_be_paused() {
let expectation = XCTestExpectation()
holder.stateUpdate = { state in
switch state {
case .playing: self.wrapper.pause()
case .paused: expectation.fulfill()
default: break
}
}
wrapper.load(from: Source.url, playWhenReady: true)
wait(for: [expectation], timeout: 20.0)
}
func test_AVPlayerWrapper__state__when_toggling_from_play__should_be_paused() {
let expectation = XCTestExpectation()
holder.stateUpdate = { state in
switch state {
case .playing: self.wrapper.togglePlaying()
case .paused: expectation.fulfill()
default: break
}
}
wrapper.load(from: Source.url, playWhenReady: true)
wait(for: [expectation], timeout: 20.0)
}
func test_AVPlayerWrapper__state__when_stopping__should_be_stopped() {
let expectation = XCTestExpectation()
holder.stateUpdate = { state in
switch state {
case .playing: self.wrapper.stop()
case .idle: expectation.fulfill()
default: break
}
}
wrapper.load(from: Source.url, playWhenReady: true)
wait(for: [expectation], timeout: 20.0)
}
func test_AVPlayerWrapper__state__loading_with_intial_time__should_be_playing() {
let expectation = XCTestExpectation()
holder.stateUpdate = { state in
switch state {
case .playing: expectation.fulfill()
default: break
}
}
wrapper.load(from: LongSource.url, playWhenReady: true, initialTime: 4.0)
wait(for: [expectation], timeout: 20.0)
}
// MARK: - Duration tests
func test_AVPlayerWrapper__duration__should_be_0() {
XCTAssert(wrapper.duration == 0.0)
}
func test_AVPlayerWrapper__duration__loading_a_source__should_not_be_0() {
let expectation = XCTestExpectation()
holder.stateUpdate = { _ in
if self.wrapper.duration > 0 {
expectation.fulfill()
}
}
wrapper.load(from: Source.url, playWhenReady: false)
wait(for: [expectation], timeout: 20.0)
}
// MARK: - Current time tests
func test_AVPlayerWrapper__currentTime__should_be_0() {
XCTAssert(wrapper.currentTime == 0)
}
// MARK: - Seeking
func test_AVPlayerWrapper__seeking__should_seek() {
let seekTime: TimeInterval = 5.0
let expectation = XCTestExpectation()
holder.stateUpdate = { state in
self.wrapper.seek(to: seekTime)
}
holder.didSeekTo = { seconds in
expectation.fulfill()
}
wrapper.load(from: Source.url, playWhenReady: false)
wait(for: [expectation], timeout: 20.0)
}
func test_AVPlayerWrapper__loading_source_with_initial_time__should_seek() {
let expectation = XCTestExpectation()
holder.didSeekTo = { seconds in
expectation.fulfill()
}
wrapper.load(from: LongSource.url, playWhenReady: false, initialTime: 4.0)
wait(for: [expectation], timeout: 20.0)
}
// MARK: - Rate tests
func test_AVPlayerWrapper__rate__should_be_0() {
XCTAssert(wrapper.rate == 0.0)
}
func test_AVPlayerWrapper__rate__playing_a_source__should_be_1() {
let expectation = XCTestExpectation()
holder.stateUpdate = { state in
if self.wrapper.rate == 1.0 {
expectation.fulfill()
}
}
wrapper.load(from: Source.url, playWhenReady: true)
wait(for: [expectation], timeout: 20.0)
}
func test_AVPlayerWrapper__timeObserver__when_updated__should_update_the_observers_periodicObserverTimeInterval() {
wrapper.timeEventFrequency = .everySecond
XCTAssert(wrapper.playerTimeObserver.periodicObserverTimeInterval == TimeEventFrequency.everySecond.getTime())
wrapper.timeEventFrequency = .everyHalfSecond
XCTAssert(wrapper.playerTimeObserver.periodicObserverTimeInterval == TimeEventFrequency.everyHalfSecond.getTime())
}
}
class AVPlayerWrapperDelegateHolder: AVPlayerWrapperDelegate {
func AVWrapper(itemPlaybackDoneWithReason reason: PlaybackEndedReason) {
func AVWrapperDidRecreateAVPlayer() {
}
func AVWrapperItemDidPlayToEndTime() {
}
@@ -232,6 +199,8 @@ class AVPlayerWrapperDelegateHolder: AVPlayerWrapperDelegate {
}
var stateUpdate: ((_ state: AVPlayerWrapperState) -> Void)?
var didUpdateDuration: ((_ duration: Double) -> Void)?
var didSeekTo: ((_ seconds: Int) -> Void)?
var itemDidComplete: (() -> Void)?
func AVWrapper(didChangeState state: AVPlayerWrapperState) {
@@ -246,16 +215,15 @@ class AVPlayerWrapperDelegateHolder: AVPlayerWrapperDelegate {
}
var seekCompletion: (() -> Void)?
func AVWrapper(seekTo seconds: Int, didFinish: Bool) {
seekCompletion?()
didSeekTo?(seconds)
}
func AVWrapper(didUpdateDuration duration: Double) {
if let state = self.state {
self.stateUpdate?(state)
}
didUpdateDuration?(duration)
}
}
+81
View File
@@ -0,0 +1,81 @@
import Quick
import Nimble
import MediaPlayer
@testable import SwiftAudio
class AudioPlayerEventTests: QuickSpec {
class EventListener {
var handleEvent: ((Void)) -> Void = { _ in
}
}
override func spec() {
describe("An event") {
var event: AudioPlayer.Event<(Void)>!
beforeEach {
event = AudioPlayer.Event()
}
describe("its invokers", {
context("when adding a listener", {
var listener: EventListener!
beforeEach {
listener = EventListener()
event.addListener(listener, listener!.handleEvent)
}
it("should have one element", closure: {
expect(event.invokers.count).toEventuallyNot(equal(0))
})
context("then that listener is deinitialized and an an event is emitted", {
beforeEach {
listener = nil
event.emit(data: ())
}
it("should remove the invoker", closure: {
expect(event.invokers.count).toEventually(equal(0))
})
})
})
context("when adding multiple listeners", {
var listeners: [EventListener]!
beforeEach {
listeners = [0..<15].map {_ in
let listener = EventListener()
event.addListener(listener, listener.handleEvent)
return listener
}
}
it("should have several listeners", closure: {
expect(event.invokers.count).toEventually(equal(listeners.count))
})
context("then removing one", {
beforeEach {
event.removeListener(listeners[listeners.count / 2])
}
it("should have one less invoker", closure: {
expect(event.invokers.count).toEventually(equal(listeners.count - 1))
})
})
})
})
}
}
}
+181 -170
View File
@@ -1,169 +1,175 @@
import Quick
import Nimble
import AVFoundation
import XCTest
@testable import SwiftAudio
class AudioPlayerTests: QuickSpec {
class AudioPlayerTests: XCTestCase {
override func spec() {
describe("An AudioPlayer") {
var audioPlayer: AudioPlayer!
beforeEach {
audioPlayer = AudioPlayer()
audioPlayer.bufferDuration = 0.0001
audioPlayer.automaticallyWaitsToMinimizeStalling = false
audioPlayer.volume = 0.0
var audioPlayer: AudioPlayer!
var listener: AudioPlayerEventListener!
override func setUp() {
super.setUp()
audioPlayer = AudioPlayer()
audioPlayer.volume = 0.0
audioPlayer.bufferDuration = 0.001
audioPlayer.automaticallyWaitsToMinimizeStalling = false
listener = AudioPlayerEventListener(audioPlayer: audioPlayer)
}
override func tearDown() {
audioPlayer = nil
listener = nil
super.tearDown()
}
func test_AudioPlayer__state__should_be_idle() {
XCTAssert(audioPlayer.playerState == AudioPlayerState.idle)
}
func test_AudioPlayer__state__load_source__should_be_loading() {
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
XCTAssertEqual(audioPlayer.playerState, AudioPlayerState.loading)
}
func test_AudioPlayer__state__load_source__should_be_ready() {
let expectation = XCTestExpectation()
listener.stateUpdate = { state in
switch state {
case .ready: expectation.fulfill()
default: break
}
describe("its state", {
it("should be idle", closure: {
expect(audioPlayer.playerState).to(equal(AudioPlayerState.idle))
})
context("when audio item is loaded", {
beforeEach {
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
}
it("it should eventually be ready", closure: {
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.ready))
})
})
context("when an item is loaded (playWhenReady=true)", {
beforeEach {
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
}
it("it should eventually be playing", closure: {
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
})
})
context("when playing an item", {
var holder: AudioPlayerDelegateHolder!
beforeEach {
holder = AudioPlayerDelegateHolder()
audioPlayer.delegate = holder
holder.stateUpdate = { state in
print(state.rawValue)
if state == .ready {
try? audioPlayer.play()
}
}
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
}
it("should eventually be playing", closure: {
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.playing))
})
})
context("when pausing an item", {
var holder: AudioPlayerDelegateHolder!
beforeEach {
holder = AudioPlayerDelegateHolder()
audioPlayer.delegate = holder
holder.stateUpdate = { (state) in
if state == .playing {
try? audioPlayer.pause()
}
}
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
}
it("should eventually be paused", closure: {
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.paused))
})
})
context("when stopping an item", {
var holder: AudioPlayerDelegateHolder!
beforeEach {
holder = AudioPlayerDelegateHolder()
audioPlayer.delegate = holder
holder.stateUpdate = { (state) in
if state == .playing {
audioPlayer.stop()
}
}
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
}
it("should eventually be idle", closure: {
expect(audioPlayer.playerState).toEventually(equal(AudioPlayerState.idle))
})
})
})
describe("its current time", {
it("should be 0", closure: {
expect(audioPlayer.currentTime).to(equal(0))
})
context("when seeking to a time", {
var passed = false
let holder = AudioPlayerDelegateHolder()
let seekTime: TimeInterval = 0.5
beforeEach {
audioPlayer.delegate = holder
holder.seekCompletion = { passed = true }
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
audioPlayer.seek(to: seekTime)
}
it("should eventually be equal to the seeked time", closure: {
expect(passed).toEventually(beTrue())
})
})
})
describe("its rate", {
it("should be 0", closure: {
expect(audioPlayer.rate).to(equal(0))
})
context("when playing an item", {
beforeEach {
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
}
it("should eventually be 1.0", closure: {
expect(audioPlayer.rate).toEventually(equal(1.0))
})
})
})
describe("its currentItem", {
it("should be nil", closure: {
expect(audioPlayer.currentItem).to(beNil())
})
context("when loading an item", {
beforeEach {
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
}
it("should not be nil", closure: {
expect(audioPlayer.currentItem).toNot(beNil())
})
})
})
}
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
wait(for: [expectation], timeout: 20.0)
}
func test_AudioPlayer__state__load_source_playWhenReady__should_be_playing() {
let expectation = XCTestExpectation()
listener.stateUpdate = { state in
switch state {
case .playing: expectation.fulfill()
default: break
}
}
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
wait(for: [expectation], timeout: 20.0)
}
func test_AudioPlayer__state__play_source__should_be_playing() {
let expectation = XCTestExpectation()
listener.stateUpdate = { state in
switch state {
case .ready: self.audioPlayer.play()
case .playing: expectation.fulfill()
default: break
}
}
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
wait(for: [expectation], timeout: 20.0)
}
func test_AudioPlayer__state__pausing_source__should_be_paused() {
let expectation = XCTestExpectation()
listener.stateUpdate = { [weak audioPlayer] state in
switch state {
case .playing: audioPlayer?.pause()
case .paused: expectation.fulfill()
default: break
}
}
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
wait(for: [expectation], timeout: 20.0)
}
func test_AudioPlayer__state__stopping_source__should_be_idle() {
let expectation = XCTestExpectation()
var hasBeenPlaying: Bool = false
listener.stateUpdate = { [weak audioPlayer] state in
switch state {
case .playing:
hasBeenPlaying = true
audioPlayer?.stop()
case .idle:
if hasBeenPlaying {
expectation.fulfill()
}
default: break
}
}
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
wait(for: [expectation], timeout: 20.0)
}
// MARK: - Current time
func test_AudioPlayer__currentTime__should_be_0() {
XCTAssert(audioPlayer.currentTime == 0.0)
}
// Commented out -- Keeps failing in CI at Bitrise, but succeeds locally, even with Bitrise CLI.
// func test_AudioPlayer__currentTime__playing_source__shold_be_greater_than_0() {
// let expectation = XCTestExpectation()
// audioPlayer.timeEventFrequency = .everyQuarterSecond
// listener.secondsElapse = { _ in
// if self.audioPlayer.currentTime > 0.0 {
// expectation.fulfill()
// }
// }
// try? audioPlayer.load(item: LongSource.getAudioItem(), playWhenReady: true)
// wait(for: [expectation], timeout: 20.0)
// }
// MARK: - Rate
func test_AudioPlayer__rate__should_be_0() {
XCTAssert(audioPlayer.rate == 0.0)
}
func test_AudioPlayer__rate__playing_source__should_be_1() {
let expectation = XCTestExpectation()
listener.stateUpdate = { [weak audioPlayer] state in
guard let audioPlayer = audioPlayer else { return }
switch state {
case .playing:
if audioPlayer.rate == 1.0 {
expectation.fulfill()
}
default: break
}
}
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
wait(for: [expectation], timeout: 20.0)
}
// MARK: - Current item
func test_AudioPlayer__currentItem__should_be_nil() {
XCTAssertNil(audioPlayer.currentItem)
}
func test_AudioPlayer__currentItem__loading_source__should_not_be_nil() {
let expectation = XCTestExpectation()
listener.stateUpdate = { [weak audioPlayer] state in
guard let audioPlayer = audioPlayer else { return }
switch state {
case .ready:
if audioPlayer.currentItem != nil {
expectation.fulfill()
}
default: break
}
}
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
wait(for: [expectation], timeout: 20.0)
}
}
class AudioPlayerDelegateHolder: AudioPlayerDelegate {
func audioPlayer(itemPlaybackEndedWithReason reason: PlaybackEndedReason) {
}
class AudioPlayerEventListener {
var stateUpdate: ((_ state: AudioPlayerState) -> Void)?
var state: AudioPlayerState? {
didSet {
if let state = state {
@@ -172,29 +178,34 @@ class AudioPlayerDelegateHolder: AudioPlayerDelegate {
}
}
func audioPlayer(playerDidChangeState state: AudioPlayerState) {
var stateUpdate: ((_ state: AudioPlayerState) -> Void)?
var secondsElapse: ((_ seconds: TimeInterval) -> Void)?
var seekCompletion: (() -> Void)?
weak var audioPlayer: AudioPlayer?
init(audioPlayer: AudioPlayer) {
audioPlayer.event.stateChange.addListener(self, handleDidUpdateState)
audioPlayer.event.seek.addListener(self, handleSeek)
audioPlayer.event.secondElapse.addListener(self, handleSecondsElapse)
}
deinit {
audioPlayer?.event.stateChange.removeListener(self)
audioPlayer?.event.seek.removeListener(self)
audioPlayer?.event.secondElapse.removeListener(self)
}
func handleDidUpdateState(state: AudioPlayerState) {
self.state = state
}
func audioPlayer(secondsElapsed seconds: Double) {
}
func audioPlayer(failedWithError error: Error?) {
}
var seekCompletion: (() -> Void)?
func audioPlayer(seekTo seconds: Int, didFinish: Bool) {
func handleSeek(data: AudioPlayer.SeekEventData) {
seekCompletion?()
}
func audioPlayer(didUpdateDuration duration: Double) {
if let state = self.state {
self.stateUpdate?(state)
}
func handleSecondsElapse(data: AudioPlayer.SecondElapseEventData) {
self.secondsElapse?(data)
}
}
@@ -55,7 +55,7 @@ class AudioSessionControllerTests: QuickSpec {
context("when a interruption arrives", {
var delegate: AudioSessionControllerDelegateImplementation!
beforeEach {
let notification = Notification(name: .AVAudioSessionInterruption, object: nil, userInfo: [
let notification = Notification(name: AVAudioSession.interruptionNotification, object: nil, userInfo: [
AVAudioSessionInterruptionTypeKey: UInt(0)
])
delegate = AudioSessionControllerDelegateImplementation()
@@ -92,9 +92,9 @@ class AudioSessionControllerTests: QuickSpec {
class AudioSessionControllerDelegateImplementation: AudioSessionControllerDelegate {
var interruptionType: AVAudioSessionInterruptionType? = nil
var interruptionType: AVAudioSession.InterruptionType? = nil
func handleInterruption(type: AVAudioSessionInterruptionType) {
func handleInterruption(type: AVAudioSession.InterruptionType) {
self.interruptionType = type
}
}
+22 -10
View File
@@ -14,31 +14,43 @@ import AVFoundation
class NonFailingAudioSession: AudioSession {
var category: AVAudioSession.Category = AVAudioSession.Category.playback
var mode: AVAudioSession.Mode = AVAudioSession.Mode.default
var categoryOptions: AVAudioSession.CategoryOptions = []
var availableCategories: [AVAudioSession.Category] = []
var isOtherAudioPlaying: Bool = false
var availableCategories: [String] = []
func setCategory(_ category: AVAudioSession.Category, mode: AVAudioSession.Mode, options: AVAudioSession.CategoryOptions) throws {}
func setCategory(_ category: String) throws {}
func setCategory(_ category: String, mode: String, options: AVAudioSessionCategoryOptions) throws {}
func setCategory(_ category: AVAudioSession.Category, mode: AVAudioSession.Mode, policy: AVAudioSession.RouteSharingPolicy, options: AVAudioSession.CategoryOptions) throws {}
func setActive(_ active: Bool) throws {}
func setActive(_ active: Bool, with options: AVAudioSessionSetActiveOptions) throws {}
func setActive(_ active: Bool, options: AVAudioSession.SetActiveOptions) throws {}
}
class FailingAudioSession: AudioSession {
var category: AVAudioSession.Category = AVAudioSession.Category.playback
var mode: AVAudioSession.Mode = AVAudioSession.Mode.default
var categoryOptions: AVAudioSession.CategoryOptions = AVAudioSession.CategoryOptions.allowBluetooth
var availableCategories: [AVAudioSession.Category] = []
var isOtherAudioPlaying: Bool = false
var availableCategories: [String] = []
func setCategory(_ category: String) throws {
func setCategory(_ category: AVAudioSession.Category, mode: AVAudioSession.Mode, options: AVAudioSession.CategoryOptions) throws {
throw AVError(AVError.unknown)
}
func setCategory(_ category: String, mode: String, options: AVAudioSessionCategoryOptions) throws {
func setCategory(_ category: AVAudioSession.Category, mode: AVAudioSession.Mode, policy: AVAudioSession.RouteSharingPolicy, options: AVAudioSession.CategoryOptions) throws {
throw AVError(AVError.unknown)
}
@@ -46,7 +58,7 @@ class FailingAudioSession: AudioSession {
throw AVError(AVError.unknown)
}
func setActive(_ active: Bool, with options: AVAudioSessionSetActiveOptions) throws {
func setActive(_ active: Bool, options: AVAudioSession.SetActiveOptions) throws {
throw AVError(AVError.unknown)
}
@@ -0,0 +1,18 @@
//
// NowPlayingInfoCenter.swift
// SwiftAudio_Tests
//
// Created by Jørgen Henrichsen on 03/03/2019.
// Copyright © 2019 CocoaPods. All rights reserved.
//
import Foundation
import AVFoundation
@testable import SwiftAudio
class NowPlayingInfoCenter_Mock: NowPlayingInfoCenter {
var nowPlayingInfo: [String : Any]? = nil
}
@@ -0,0 +1,66 @@
//
// NowPlayingInfoController.swift
// SwiftAudio_Tests
//
// Created by Jørgen Henrichsen on 03/03/2019.
// Copyright © 2019 CocoaPods. All rights reserved.
//
import Foundation
import MediaPlayer
@testable import SwiftAudio
class NowPlayingInfoController_Mock: NowPlayingInfoControllerProtocol {
var info: [String: Any] = [:]
required public init() {
}
required public init(infoCenter: NowPlayingInfoCenter) {
}
public func set(keyValues: [NowPlayingInfoKeyValue]) {
keyValues.forEach { (keyValue) in
info[keyValue.getKey()] = keyValue.getValue()
}
}
public func set(keyValue: NowPlayingInfoKeyValue) {
info[keyValue.getKey()] = keyValue.getValue()
}
func getTitle() -> String? {
return info[MediaItemProperty.title(nil).getKey()] as? String
}
func getArtist() -> String? {
return info[MediaItemProperty.artist(nil).getKey()] as? String
}
func getAlbumTitle() -> String? {
return info[MediaItemProperty.albumTitle(nil).getKey()] as? String
}
func getRate() -> Double? {
return info[NowPlayingInfoProperty.playbackRate(nil).getKey()] as? Double
}
func getDuration() -> Double? {
return info[MediaItemProperty.duration(nil).getKey()] as? Double
}
func getCurrentTime() -> Double? {
return info[NowPlayingInfoProperty.elapsedPlaybackTime(nil).getKey()] as? Double
}
func getArtwork() -> MPMediaItemArtwork? {
return info[MediaItemProperty.artwork(nil).getKey()] as? MPMediaItemArtwork
}
func clear() {
info = [:]
}
}
@@ -0,0 +1,72 @@
import Quick
import Nimble
import MediaPlayer
@testable import SwiftAudio
class NowPlayingInfoControllerTests: QuickSpec {
override func spec() {
describe("An NowPlayingInfoController") {
var nowPlayingController: NowPlayingInfoController!
beforeEach {
nowPlayingController = NowPlayingInfoController(infoCenter: NowPlayingInfoCenter_Mock())
}
describe("its info dictionary") {
context("when setting a value") {
beforeEach {
nowPlayingController.set(keyValue: MediaItemProperty.title("Some title"))
}
it("should not be empty") {
expect(nowPlayingController.info.count).toNot(equal(0))
}
context("then calling clear()") {
beforeEach {
nowPlayingController.clear()
}
it("should be empty", closure: {
expect(nowPlayingController.info.count).to(equal(0))
})
}
}
}
describe("its info center") {
context("when setting a value") {
beforeEach {
nowPlayingController.set(keyValue: MediaItemProperty.title("Some title"))
}
it("should not be nil") {
expect(nowPlayingController.infoCenter.nowPlayingInfo).toNot(beNil())
}
it("should not be empty") {
expect(nowPlayingController.infoCenter.nowPlayingInfo?.count).toNot(equal(0))
}
context("then calling clear()") {
beforeEach {
nowPlayingController.clear()
}
it("should be empty", closure: {
expect(nowPlayingController.infoCenter.nowPlayingInfo?.count).to(equal(0))
})
}
}
}
}
}
}
+73
View File
@@ -0,0 +1,73 @@
import Quick
import Nimble
import MediaPlayer
@testable import SwiftAudio
/// Tests that the AudioPlayer is automatically updating the values it should update in the NowPlayingInfoController.
class NowPlayingInfoTests: QuickSpec {
override func spec() {
describe("An AudioPlayer") {
var audioPlayer: AudioPlayer!
var nowPlayingController: NowPlayingInfoController_Mock!
beforeEach {
nowPlayingController = NowPlayingInfoController_Mock()
audioPlayer = AudioPlayer(nowPlayingInfoController: nowPlayingController)
audioPlayer.automaticallyUpdateNowPlayingInfo = true
audioPlayer.volume = 0
}
describe("its NowPlayingInfoController", {
context("when loading an AudioItem", {
var item: AudioItem!
beforeEach {
item = Source.getAudioItem()
try? audioPlayer.load(item: item, playWhenReady: false)
}
it("should eventually be updated with meta data", closure: {
expect(nowPlayingController.getTitle()).toEventuallyNot(beNil())
expect(nowPlayingController.getTitle()).toEventually(equal(item.getTitle()!))
expect(nowPlayingController.getArtist()).toEventuallyNot(beNil())
expect(nowPlayingController.getArtist()).toEventually(equal(item.getArtist()!))
expect(nowPlayingController.getAlbumTitle()).toEventuallyNot(beNil())
expect(nowPlayingController.getAlbumTitle()).toEventually(equal(item.getAlbumTitle()!))
expect(nowPlayingController.getArtwork()).toEventuallyNot(beNil())
})
})
context("when playing an AudioItem", {
var item: AudioItem!
beforeEach {
item = LongSource.getAudioItem()
try? audioPlayer.load(item: item, playWhenReady: true)
}
it("should eventually be updated with playback values", closure: {
expect(nowPlayingController.getRate()).toEventuallyNot(beNil())
expect(nowPlayingController.getDuration()).toEventuallyNot(beNil())
expect(nowPlayingController.getCurrentTime()).toEventuallyNot(beNil())
})
})
})
}
}
}
+2 -2
View File
@@ -83,7 +83,7 @@ class QueueManagerTests: QuickSpec {
context("then replacing the item", closure: {
beforeEach {
try? manager.replaceCurrentItem(with: 1)
manager.replaceCurrentItem(with: 1)
}
it("should have replaced the current item", closure: {
expect(manager.current).to(equal(1))
@@ -217,7 +217,7 @@ class QueueManagerTests: QuickSpec {
var removed: Int?
var initialCurrentIndex: Int!
beforeEach {
try? manager.jump(to: 3)
let _ = try? manager.jump(to: 3)
initialCurrentIndex = manager.currentIndex
removed = try? manager.removeItem(at: initialCurrentIndex - 1)
}
+12 -3
View File
@@ -14,15 +14,24 @@ struct Source {
static let url: URL = URL(fileURLWithPath: Source.path)
static func getAudioItem() -> AudioItem {
return DefaultAudioItem(audioUrl: Source.path, sourceType: .file, pitchAlgorithmType: .lowQualityZeroLatency)
return DefaultAudioItem(audioUrl: Source.path, artist: "Artist", title: "Title", albumTitle: "AlbumTitle", sourceType: .file, artwork: UIImage())
}
}
struct ShortSource {
static let path: String = Bundle.main.path(forResource: "ShortTestSound", ofType: "m4a")!
static let url: URL = URL(fileURLWithPath: Source.path)
static let url: URL = URL(fileURLWithPath: ShortSource.path)
static func getAudioItem() -> AudioItem {
return DefaultAudioItem(audioUrl: ShortSource.path, sourceType: .file, pitchAlgorithmType: .lowQualityZeroLatency)
return DefaultAudioItem(audioUrl: ShortSource.path, sourceType: .file)
}
}
struct LongSource {
static let path: String = Bundle.main.path(forResource: "WAV-MP3", ofType: "wav")!
static let url: URL = URL(fileURLWithPath: LongSource.path)
static func getAudioItem() -> AudioItem {
return DefaultAudioItem(audioUrl: LongSource.path, sourceType: .file)
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Some files were not shown because too many files have changed in this diff Show More