Compare commits

..

110 Commits

Author SHA1 Message Date
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
Jørgen Henrichsen 0694f5d8a0 Update Readme. Update podspec.
Bumping versions to 0.4.2
2018-11-06 22:41:13 +01:00
Jørgen Henrichsen 61268b45eb Merge pull request #32 from jorgenhenrichsen/queue-clearing
Queue functions
2018-11-06 22:40:07 +01:00
Jørgen Henrichsen 962e64fe24 Merge branch 'queue-clearing' of github.com:jorgenhenrichsen/SwiftAudio into queue-clearing 2018-11-06 22:31:05 +01:00
Jørgen Henrichsen 0f9b2656a1 Test remove upcoming/next items and clear queue on stop. 2018-11-06 22:30:15 +01:00
Jørgen Henrichsen 5e871fc7e2 Merge branch 'master' into queue-clearing 2018-11-06 21:30:27 +01:00
Jørgen Henrichsen fdf8fc9482 Added tests for removePreviousItems() 2018-11-06 21:27:07 +01:00
Jørgen Henrichsen a9eb713964 Remove upcoming items. Clear queue on reset 2018-11-04 15:21:24 +01:00
Jørgen Henrichsen a0efa5f408 Update README
Bump Carthage install release version
2018-11-02 19:26:03 +01:00
Jørgen Henrichsen 74cafd4c42 Merge pull request #31 from jorgenhenrichsen/feature/support-carthage
Feature/support carthage
2018-11-02 19:18:23 +01:00
Jørgen Henrichsen 53780ac03e Update Readme
Added info about installing via Carthage.
2018-11-02 18:24:31 +01:00
Jørgen Henrichsen ba438c8ede Set SwiftAudio scheme as shared. 2018-11-02 17:53:37 +01:00
Jørgen Henrichsen 06cb6577c1 Merge pull request #30 from jorgenhenrichsen/dev
Dev into master
2018-11-02 15:33:25 +01:00
Jørgen Henrichsen 5a0f379275 Update podspec. Update README.
- Bumped version to 0.4.0
- Updated the readme iwth new content and clarifications
2018-11-02 15:12:22 +01:00
Jørgen Henrichsen 11e793f963 Fixed typo. Added docs.
- Fixed type in the `timeEventFrequency`-property
- Added doc comment to `automaticallyWaitsToMinimizeStalling`
2018-11-02 15:11:01 +01:00
Jørgen Henrichsen d094067ac7 Use old syntax for AVAudioSession Options and Category 2018-11-02 13:54:07 +01:00
Jørgen Henrichsen 4bacd5f1ff Fixed problem in addItems(). Added more tests.
- QueueManager's addItems() incremented the currentIndex with the managers item count. Corrected to only increment by the added item count.
- Added tests for the QueueManager.
2018-11-02 13:13:50 +01:00
Jørgen Henrichsen 73089bbe8b Use AudioSession protocol instead of AVAudioSession.
- Makes the AudioSessionController easier to test.
- Updated tests
- Fixes a problem where the AudioSessionController tests would not succeed on device.
2018-11-02 08:01:15 +01:00
Jørgen Henrichsen b4f919e8f4 Merge branch 'master' into dev 2018-10-29 20:56:14 +01:00
Jørgen Henrichsen 7e33789644 Update README
Change install instructions to use 0.3.6
2018-10-29 19:10:19 +01:00
Jørgen Henrichsen 843ba9f450 Bumped version to 0.3.6 2018-10-29 18:53:26 +01:00
Jørgen Henrichsen 4305110867 Merge pull request #28 from dcvz/master
Add a Couple of Features and Fixes
2018-10-29 18:50:48 +01:00
David Chavez cd43ecc6f9 Update example and fix tests 2018-10-29 15:44:06 +01:00
David Chavez 274377af3f Address review comments 2018-10-29 14:21:09 +01:00
Jørgen Henrichsen ef41885eaf Merge pull request #29 from jorgenhenrichsen/SwiftAudio-1
Restructuring and simplifying
2018-10-28 22:54:06 +01:00
Jørgen Henrichsen 21da2da43b Update README
Added part about AudioPlayerDelegate and adding additional NowPlayingInfo
2018-10-28 22:35:02 +01:00
Jørgen Henrichsen 336d5586bf Added a couple of tests to the AVPlayerWrapper 2018-10-28 22:29:29 +01:00
Jørgen Henrichsen e917867220 Updated tests 2018-10-28 19:24:20 +01:00
Jørgen Henrichsen 7897a79a79 Added options for volume, muting and automaticallyWaitingToMinimizeStalling 2018-10-28 19:20:14 +01:00
David Chavez 43824a9700 Add granularity for a track finishing playback between simple/queued 2018-10-28 13:37:02 +01:00
Jørgen Henrichsen 460af7ab1e Added tests for the currentItem in the AudioPlayer 2018-10-28 12:44:52 +01:00
Jørgen Henrichsen 670bf0e09e Made enable(disable commands internal 2018-10-28 12:42:21 +01:00
Jørgen Henrichsen 238c02db12 Removed unneccessary comments.
Made the enable commands method private, as there is no need for it to be public.
2018-10-28 12:38:43 +01:00
Jørgen Henrichsen 20190152eb Remove the add(propert:) for adding new NowPlayingInfo properties.
Made the nowPlayingInfoController public.
2018-10-28 12:30:47 +01:00
David Chavez 9e6683674b Fix issues with updating index upon queue mutations 2018-10-28 12:02:45 +01:00
David Chavez b27332aafb Add a couple of features and fix some issues 2018-10-28 10:35:44 +01:00
Jørgen Henrichsen 18688ab766 QueuedAudioPlayer load(item:) will replace the current item in the queue. 2018-10-26 14:07:47 +02:00
Jørgen Henrichsen 6835738754 Update README 2018-10-26 13:23:07 +02:00
Jørgen Henrichsen 9f05915993 Removed testing code snippet breaking the AVPlayerWrapper 2018-10-26 13:12:38 +02:00
Jørgen Henrichsen 2f9a5481ff Removed warnings 2018-10-26 13:09:49 +02:00
Jørgen Henrichsen 079f8c8ce1 Update travis.yml
Change back to Xcode 9
2018-10-26 12:57:38 +02:00
Jørgen Henrichsen 720b4739b4 Update travis.yml
Roll back to iOS 11.4 simulator for testing, as there is an issue with loading duration on the player with iOS 12 simulators #26
2018-10-26 12:52:42 +02:00
Jørgen Henrichsen 2b150b5652 Merge branch 'SwiftAudio-1' of github.com:jorgenhenrichsen/SwiftAudio into SwiftAudio-1 2018-10-26 12:25:14 +02:00
Jørgen Henrichsen 4adc84aaf3 Moved files to correct folder. 2018-10-26 12:24:48 +02:00
Jørgen Henrichsen 76f2d22e7a Update travis.yml
Set SDK version to iOS 12.0
2018-10-26 12:18:40 +02:00
Jørgen Henrichsen 3c409200ee Update travis.yml
Change osx_image to Xcode 10.
Change iOS version to iOS 12.
2018-10-26 12:13:36 +02:00
Jørgen Henrichsen b44777fcd7 Changed method signature for loading items. 2018-10-26 11:28:55 +02:00
Jørgen Henrichsen d682fd9468 Removed outdated comment. 2018-10-26 11:22:30 +02:00
Jørgen Henrichsen 10e6c46c18 Updated AudioPlayer tests 2018-10-26 11:18:26 +02:00
Jørgen Henrichsen 89715d9d38 Made it possible to supply custom AVPlayer, NowPlayingInfoController and RemoteCommandController in the AudioPlayer init(). 2018-10-26 11:11:58 +02:00
Jørgen Henrichsen 99bd43769c Folder structure in the project 2018-10-26 11:00:15 +02:00
Jørgen Henrichsen ce5e5e886f Removed SimpleAudioPlayer, will use AudioPlayer from now on.
Removed the tests.
2018-10-26 10:57:31 +02:00
Jørgen Henrichsen 521141ba0d Moved the AVPlayerWrapperDelegate to a seperate file. 2018-10-26 10:54:08 +02:00
Jørgen Henrichsen d3d354d5bd Simplified AVPlayerWrapper.
Created a protocol for the wrapper to follow.
Removed config options that where just getters/setters to the underlying AVPlayer.
Made it possible to pass in custom AVPlayer that can be customized with these options.
Updated AVPlayerWrapperTests and added a couple tests for the rate property.
2018-10-26 10:50:55 +02:00
Jørgen Henrichsen 664c56b79c Update README
Add version to pod file instruction
2018-10-25 10:49:53 +02:00
Jørgen Henrichsen 7bb83e87e0 Merge pull request #25 from jorgenhenrichsen/dev
Update master
2018-10-23 10:12:46 +02:00
Jørgen Henrichsen 6cad96b4f8 Update podscpec
Bump version to 0.3.5
2018-10-22 14:20:40 +02:00
Jørgen Henrichsen 50a58c2306 Merge pull request #24 from jorgenhenrichsen/handle-remote-commands
Handle remote commands
2018-10-22 14:18:17 +02:00
Jørgen Henrichsen 2dbaf3d4dc Update Readme
Add description of how to override remote command handlers.
2018-10-22 10:11:04 +02:00
Jørgen Henrichsen 851d8704d9 Made remotecommandcontroller public. 2018-10-21 19:44:24 +02:00
Jørgen Henrichsen 7c4dc27868 Made it possible to override remote command handlers 2018-10-21 10:42:29 +02:00
jorgenhenrichsen 0df6386786 Merge pull request #23 from jorgenhenrichsen/dev
Dev
2018-10-10 12:36:46 +02:00
jorgenhenrichsen b58c40e7c3 Merge branch 'master' into dev 2018-10-10 12:16:22 +02:00
Jørgen Henrichsen 77a3206633 Update podspec
Bmump version to 0.3.4
2018-10-10 12:05:55 +02:00
jorgenhenrichsen d7c8c0fc0d Merge pull request #20 from jorgenhenrichsen/fix/play-pause-throwing
Only throw noLoadedItemError when no item is loaded.
2018-10-10 10:03:45 +02:00
Jørgen Henrichsen e8354dfdfb Test play()/pause() with no item loaded 2018-10-10 08:55:53 +02:00
Jørgen Henrichsen 2e9c8733fc Use guard to check for error conditions 2018-10-10 08:55:37 +02:00
jorgenhenrichsen 97353b98d9 Merge branch 'dev' into fix/play-pause-throwing 2018-10-10 07:10:24 +02:00
jorgenhenrichsen 0e3e8ec5b9 Merge pull request #22 from jorgenhenrichsen/update-pods
Updated pods
2018-10-10 07:10:03 +02:00
Jørgen Henrichsen f8dbe9d312 Update travis config
Update pod repo on pod install
2018-10-10 06:29:20 +02:00
Jørgen Henrichsen 9f4ef3aa9e Updated pods 2018-10-10 06:08:05 +02:00
Jørgen Henrichsen 4097b66aac Only throw noLoadedItemError when no item is loaded.
This error was thrown when calling pause() when already paused and play() when already playing.
2018-10-09 16:45:33 +02:00
118 changed files with 3042 additions and 2250 deletions
+1 -1
View File
@@ -1 +1 @@
4.0
4.2
+3 -3
View File
@@ -2,15 +2,15 @@
# * http://www.objc.io/issue-6/travis-ci.html
# * https://github.com/supermarin/xcpretty#usage
osx_image: xcode9.4
osx_image: xcode10.1
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
- 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
- set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace Example/SwiftAudio.xcworkspace -scheme SwiftAudio-Example -sdk iphonesimulator12.1 -destination "OS=11.4,name=iPhone X" | xcpretty
- pod lib lint
after_success:
+2 -2
View File
@@ -7,8 +7,8 @@ target 'SwiftAudio_Example' do
target 'SwiftAudio_Tests' do
inherit! :search_paths
pod 'Quick'
pod 'Nimble'
pod 'Quick', '~> 1.3.0'
pod 'Nimble' , '~> 7.3.0'
end
end
+16 -11
View File
@@ -1,22 +1,27 @@
PODS:
- Nimble (7.0.3)
- Quick (1.2.0)
- SwiftAudio (0.1.0)
- Nimble (7.3.1)
- Quick (1.3.2)
- SwiftAudio (0.3.3)
DEPENDENCIES:
- Nimble
- Quick
- Nimble (~> 7.3.0)
- Quick (~> 1.3.0)
- SwiftAudio (from `../`)
SPEC REPOS:
https://github.com/cocoapods/specs.git:
- Nimble
- Quick
EXTERNAL SOURCES:
SwiftAudio:
:path: ../
:path: "../"
SPEC CHECKSUMS:
Nimble: 7f5a9c447a33002645a071bddafbfb24ea70e0ac
Quick: 58d203b1c5e27fff7229c4c1ae445ad7069a7a08
SwiftAudio: a3a2d2c800a7d47687dcfd7c35ab757462b75856
Nimble: 04f732da099ea4d153122aec8c2a88fd0c7219ae
Quick: 2623cb30d7a7f41ca62f684f679586558f483d46
SwiftAudio: 2e712c3e04cf172d05639d7bb1516db7afd195da
PODFILE CHECKSUM: 4725c63cba8dedecdf397c1768967bd269bf4532
PODFILE CHECKSUM: 8a75946cbc65d8d98176f80a88d8363a28d118ce
COCOAPODS: 1.3.1
COCOAPODS: 1.5.3
+6 -6
View File
@@ -1,9 +1,9 @@
{
"name": "SwiftAudio",
"version": "0.1.0",
"summary": "A short description of SwiftAudio.",
"description": "TODO: Add long description of the pod here.",
"homepage": "https://github.com/rgen Henrichsen/SwiftAudio",
"version": "0.3.3",
"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",
"license": {
"type": "MIT",
"file": "LICENSE"
@@ -12,8 +12,8 @@
"Jørgen Henrichsen": "jh.henrichs@gmail.com"
},
"source": {
"git": "https://github.com/rgen Henrichsen/SwiftAudio.git",
"tag": "0.1.0"
"git": "https://github.com/jorgenhenrichsen/SwiftAudio.git",
"tag": "0.3.3"
},
"platforms": {
"ios": "10.0"
+16 -11
View File
@@ -1,22 +1,27 @@
PODS:
- Nimble (7.0.3)
- Quick (1.2.0)
- SwiftAudio (0.1.0)
- Nimble (7.3.1)
- Quick (1.3.2)
- SwiftAudio (0.3.3)
DEPENDENCIES:
- Nimble
- Quick
- Nimble (~> 7.3.0)
- Quick (~> 1.3.0)
- SwiftAudio (from `../`)
SPEC REPOS:
https://github.com/cocoapods/specs.git:
- Nimble
- Quick
EXTERNAL SOURCES:
SwiftAudio:
:path: ../
:path: "../"
SPEC CHECKSUMS:
Nimble: 7f5a9c447a33002645a071bddafbfb24ea70e0ac
Quick: 58d203b1c5e27fff7229c4c1ae445ad7069a7a08
SwiftAudio: a3a2d2c800a7d47687dcfd7c35ab757462b75856
Nimble: 04f732da099ea4d153122aec8c2a88fd0c7219ae
Quick: 2623cb30d7a7f41ca62f684f679586558f483d46
SwiftAudio: 2e712c3e04cf172d05639d7bb1516db7afd195da
PODFILE CHECKSUM: 4725c63cba8dedecdf397c1768967bd269bf4532
PODFILE CHECKSUM: 8a75946cbc65d8d98176f80a88d8363a28d118ce
COCOAPODS: 1.3.1
COCOAPODS: 1.5.3
@@ -25,13 +25,11 @@ import CwlCatchExceptionSupport
#endif
private func catchReturnTypeConverter<T: NSException>(_ type: T.Type, block: () -> Void) -> T? {
// Get the type from an *instance*, instead of a receiving the type directly
return catchExceptionOfKind(type, block) as? T
}
extension NSException {
public static func catchException(in block: () -> Void) -> Self? {
// Use a dummy instance of Self to provide the type
return catchReturnTypeConverter(self, block: block)
}
}
@@ -97,8 +97,9 @@ import Foundation
// Request the next mach message from the port
request.Head.msgh_local_port = context.currentExceptionPort
request.Head.msgh_size = UInt32(MemoryLayout<request_mach_exception_raise_t>.size)
let requestSize = request.Head.msgh_size
try kernCheck { request.withMsgHeaderPointer { requestPtr in
mach_msg(requestPtr, MACH_RCV_MSG | MACH_RCV_INTERRUPT, 0, request.Head.msgh_size, context.currentExceptionPort, 0, UInt32(MACH_PORT_NULL))
mach_msg(requestPtr, MACH_RCV_MSG | MACH_RCV_INTERRUPT, 0, requestSize, context.currentExceptionPort, 0, UInt32(MACH_PORT_NULL))
} }
// Prepare the reply structure
@@ -121,8 +122,9 @@ import Foundation
}
// Send the reply
let replySize = reply.Head.msgh_size
try kernCheck { reply.withMsgHeaderPointer { replyPtr in
mach_msg(replyPtr, MACH_SEND_MSG, reply.Head.msgh_size, 0, UInt32(MACH_PORT_NULL), 0, UInt32(MACH_PORT_NULL))
mach_msg(replyPtr, MACH_SEND_MSG, replySize, 0, UInt32(MACH_PORT_NULL), 0, UInt32(MACH_PORT_NULL))
} }
} catch let error as NSError where (error.domain == NSMachErrorDomain && (error.code == Int(MACH_RCV_PORT_CHANGED) || error.code == Int(MACH_RCV_INVALID_NAME))) {
// Port was already closed before we started or closed while we were listening.
@@ -20,6 +20,8 @@
#import <Foundation/Foundation.h>
extern bool _swift_reportFatalErrorsToDebugger;
//! Project version number for CwlUtils.
FOUNDATION_EXPORT double CwlPreconditionTestingVersionNumber;
+42 -42
View File
@@ -226,9 +226,9 @@ exception once evaluated:
// that Nimble will catch.
// (see https://github.com/Quick/Nimble/issues/220#issuecomment-172667064)
let exception = NSException(
name: NSInternalInconsistencyException,
reason: "Not enough fish in the sea.",
userInfo: ["something": "is fishy"])
name: NSInternalInconsistencyException,
reason: "Not enough fish in the sea.",
userInfo: ["something": "is fishy"])
expect { exception.raise() }.to(raiseException())
// Also, you can customize raiseException to be more specific
@@ -714,7 +714,7 @@ expect(actual) ≈ expected
expect(actual) ≈ (expected, delta)
```
(Type Option-x to get on a U.S. keyboard)
(Type <kbd>option</kbd>+<kbd>x</kbd> to get `` on a U.S. keyboard)
The former version uses the default delta of 0.0001. Here is yet another way to do this:
@@ -725,7 +725,7 @@ expect(actual) ≈ expected ± delta
expect(actual) == expected ± delta
```
(Type Option-Shift-= to get ± on a U.S. keyboard)
(Type <kbd>option</kbd>+<kbd>shift</kbd>+<kbd>=</kbd> to get `±` on a U.S. keyboard)
If you are comparing arrays of floating point numbers, you'll find the following useful:
@@ -1043,10 +1043,10 @@ let turtles: [Turtle] = functionThatReturnsSomeTurtlesInAnyOrder()
// [{color: "blue"}, {color: "green"}] or [{color: "green"}, {color: "blue"}]:
expect(turtles).to(containElementSatisfying({ turtle in
return turtle.color == "green"
return turtle.color == "green"
}))
expect(turtles).to(containElementSatisfying({ turtle in
return turtle.color == "blue"
return turtle.color == "blue"
}, "that is a turtle with color 'blue'"))
// The second matcher will incorporate the provided string in the error message
@@ -1069,10 +1069,10 @@ NSArray<Turtle *> * __nonnull turtles = functionThatReturnsSomeTurtlesInAnyOrder
// [{color: "blue"}, {color: "green"}] or [{color: "green"}, {color: "blue"}]:
expect(turtles).to(containElementSatisfying(^BOOL(id __nonnull object) {
return [[turtle color] isEqualToString:@"green"];
return [[turtle color] isEqualToString:@"green"];
}));
expect(turtles).to(containElementSatisfying(^BOOL(id __nonnull object) {
return [[turtle color] isEqualToString:@"blue"];
return [[turtle color] isEqualToString:@"blue"];
}));
```
@@ -1273,24 +1273,24 @@ value and return a `Predicate` closure. Take `equal`, for example:
// Swift
public func equal<T: Equatable>(expectedValue: T?) -> Predicate<T> {
// Can be shortened to:
// Predicate { actual in ... }
//
// But shown with types here for clarity.
return Predicate { (actual: Expression<T>) throws -> PredicateResult in
let msg = ExpectationMessage.expectedActualValueTo("equal <\(expectedValue)>")
if let actualValue = try actualExpression.evaluate() {
return PredicateResult(
bool: actualValue == expectedValue!,
message: msg
)
} else {
return PredicateResult(
status: .fail,
message: msg.appendedBeNilHint()
)
// Can be shortened to:
// Predicate { actual in ... }
//
// But shown with types here for clarity.
return Predicate { (actual: Expression<T>) throws -> PredicateResult in
let msg = ExpectationMessage.expectedActualValueTo("equal <\(expectedValue)>")
if let actualValue = try actualExpression.evaluate() {
return PredicateResult(
bool: actualValue == expectedValue!,
message: msg
)
} else {
return PredicateResult(
status: .fail,
message: msg.appendedBeNilHint()
)
}
}
}
}
```
@@ -1382,11 +1382,11 @@ custom matchers should call `actualExpression.evaluate()`:
// Swift
public func beNil<T>() -> Predicate<T> {
// Predicate.simpleNilable(..) automatically generates ExpectationMessage for
// us based on the string we provide to it. Also, the 'Nilable' postfix indicates
// that this Predicate supports matching against nil actualExpressions, instead of
// always resulting in a PredicateStatus.fail result -- which is true for
// Predicate.simple(..)
// Predicate.simpleNilable(..) automatically generates ExpectationMessage for
// us based on the string we provide to it. Also, the 'Nilable' postfix indicates
// that this Predicate supports matching against nil actualExpressions, instead of
// always resulting in a PredicateStatus.fail result -- which is true for
// Predicate.simple(..)
return Predicate.simpleNilable("be nil") { actualExpression in
let actualValue = try actualExpression.evaluate()
return PredicateStatus(bool: actualValue == nil)
@@ -1412,9 +1412,9 @@ against the one provided to the matcher function, and passes if they are the sam
// Swift
public func haveDescription(description: String) -> Predicate<Printable?> {
return Predicate.simple("have description") { actual in
return PredicateStatus(bool: actual.evaluate().description == description)
}
return Predicate.simple("have description") { actual in
return PredicateStatus(bool: actual.evaluate().description == description)
}
}
```
@@ -1489,7 +1489,7 @@ case expectedCustomValueTo(/* message: */ String, /* actual: */ String)
// Emits standard error message without mentioning the actual value
// eg - "expected to <message>"
case expectedTo(/* message: */ String, /* actual: */ String)
case expectedTo(/* message: */ String)
// ...
}
@@ -1526,13 +1526,13 @@ custom matcher. The example below defines the class method
// Swift
extension NMBObjCMatcher {
public class func beNilMatcher() -> NMBObjCMatcher {
return NMBObjCMatcher { actualBlock, failureMessage, location in
let block = ({ actualBlock() as NSObject? })
let expr = Expression(expression: block, location: location)
return beNil().matches(expr, failureMessage: failureMessage)
public class func beNilMatcher() -> NMBObjCMatcher {
return NMBObjCMatcher { actualBlock, failureMessage, location in
let block = ({ actualBlock() as NSObject? })
let expr = Expression(expression: block, location: location)
return beNil().matches(expr, failureMessage: failureMessage)
}
}
}
}
```
@@ -1551,7 +1551,7 @@ class method:
// Objective-C
FOUNDATION_EXPORT id<NMBMatcher> beNil() {
return [NMBObjCMatcher beNilMatcher];
return [NMBObjCMatcher beNilMatcher];
}
```
@@ -65,7 +65,7 @@ public func withAssertionHandler(_ tempAssertionHandler: AssertionHandler, closu
/// assertion handler when this is true. Defaults to false.
///
/// @see gatherFailingExpectations
public func gatherExpectations(silently: Bool = false, closure: @escaping () -> Void) -> [AssertionRecord] {
public func gatherExpectations(silently: Bool = false, closure: () -> Void) -> [AssertionRecord] {
let previousRecorder = NimbleEnvironment.activeInstance.assertionHandler
let recorder = AssertionRecorder()
let handlers: [AssertionHandler]
@@ -92,7 +92,7 @@ public func gatherExpectations(silently: Bool = false, closure: @escaping () ->
///
/// @see gatherExpectations
/// @see raiseException source for an example use case.
public func gatherFailingExpectations(silently: Bool = false, closure: @escaping () -> Void) -> [AssertionRecord] {
public func gatherFailingExpectations(silently: Bool = false, closure: () -> Void) -> [AssertionRecord] {
let assertions = gatherExpectations(silently: silently, closure: closure)
return assertions.filter { assertion in
!assertion.success
@@ -4,7 +4,7 @@ import Foundation
private func from(objcPredicate: NMBPredicate) -> Predicate<NSObject> {
return Predicate { actualExpression in
let result = objcPredicate.satisfies(({ try! actualExpression.evaluate() }),
let result = objcPredicate.satisfies(({ try actualExpression.evaluate() }),
location: actualExpression.location)
return result.toSwift()
}
@@ -30,13 +30,13 @@ internal struct ObjCMatcherWrapper: Matcher {
// Equivalent to Expectation, but for Nimble's Objective-C interface
public class NMBExpectation: NSObject {
internal let _actualBlock: () -> NSObject!
internal let _actualBlock: () -> NSObject?
internal var _negative: Bool
internal let _file: FileString
internal let _line: UInt
internal var _timeout: TimeInterval = 1.0
@objc public init(actualBlock: @escaping () -> NSObject!, negative: Bool, file: FileString, line: UInt) {
@objc public init(actualBlock: @escaping () -> NSObject?, negative: Bool, file: FileString, line: UInt) {
self._actualBlock = actualBlock
self._negative = negative
self._file = file
@@ -3,8 +3,8 @@ import Foundation
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
// swiftlint:disable line_length
public typealias MatcherBlock = (_ actualExpression: Expression<NSObject>, _ failureMessage: FailureMessage) -> Bool
public typealias FullMatcherBlock = (_ actualExpression: Expression<NSObject>, _ failureMessage: FailureMessage, _ shouldNotMatch: Bool) -> Bool
public typealias MatcherBlock = (_ actualExpression: Expression<NSObject>, _ failureMessage: FailureMessage) throws -> Bool
public typealias FullMatcherBlock = (_ actualExpression: Expression<NSObject>, _ failureMessage: FailureMessage, _ shouldNotMatch: Bool) throws -> Bool
// swiftlint:enable line_length
public class NMBObjCMatcher: NSObject, NMBMatcher {
@@ -24,7 +24,7 @@ public class NMBObjCMatcher: NSObject, NMBMatcher {
public convenience init(canMatchNil: Bool, matcher: @escaping MatcherBlock) {
self.init(canMatchNil: canMatchNil, matcher: matcher, notMatcher: ({ actualExpression, failureMessage in
return !matcher(actualExpression, failureMessage)
return try !matcher(actualExpression, failureMessage)
}))
}
@@ -34,9 +34,9 @@ public class NMBObjCMatcher: NSObject, NMBMatcher {
public convenience init(canMatchNil: Bool, matcher: @escaping FullMatcherBlock) {
self.init(canMatchNil: canMatchNil, matcher: ({ actualExpression, failureMessage in
return matcher(actualExpression, failureMessage, false)
return try matcher(actualExpression, failureMessage, false)
}), notMatcher: ({ actualExpression, failureMessage in
return matcher(actualExpression, failureMessage, true)
return try matcher(actualExpression, failureMessage, true)
}))
}
@@ -55,11 +55,16 @@ public class NMBObjCMatcher: NSObject, NMBMatcher {
return true
}
public func matches(_ actualBlock: @escaping () -> NSObject!, failureMessage: FailureMessage, location: SourceLocation) -> Bool {
public func matches(_ actualBlock: @escaping () -> NSObject?, failureMessage: FailureMessage, location: SourceLocation) -> Bool {
let expr = Expression(expression: actualBlock, location: location)
let result = _match(
expr,
failureMessage)
let result: Bool
do {
result = try _match(expr, failureMessage)
} catch let error {
failureMessage.stringValue = "unexpected error thrown: <\(error)>"
return false
}
if self.canMatch(Expression(expression: actualBlock, location: location), failureMessage: failureMessage) {
return result
} else {
@@ -67,11 +72,16 @@ public class NMBObjCMatcher: NSObject, NMBMatcher {
}
}
public func doesNotMatch(_ actualBlock: @escaping () -> NSObject!, failureMessage: FailureMessage, location: SourceLocation) -> Bool {
public func doesNotMatch(_ actualBlock: @escaping () -> NSObject?, failureMessage: FailureMessage, location: SourceLocation) -> Bool {
let expr = Expression(expression: actualBlock, location: location)
let result = _doesNotMatch(
expr,
failureMessage)
let result: Bool
do {
result = try _doesNotMatch(expr, failureMessage)
} catch let error {
failureMessage.stringValue = "unexpected error thrown: <\(error)>"
return false
}
if self.canMatch(Expression(expression: actualBlock, location: location), failureMessage: failureMessage) {
return result
} else {
@@ -42,12 +42,23 @@ class NimbleXCTestUnavailableHandler: AssertionHandler {
private(set) var currentTestCase: XCTestCase?
private var stashed_swift_reportFatalErrorsToDebugger: Bool = false
@objc func testCaseWillStart(_ testCase: XCTestCase) {
#if swift(>=3.2)
stashed_swift_reportFatalErrorsToDebugger = _swift_reportFatalErrorsToDebugger
_swift_reportFatalErrorsToDebugger = false
#endif
currentTestCase = testCase
}
@objc func testCaseDidFinish(_ testCase: XCTestCase) {
currentTestCase = nil
#if swift(>=3.2)
_swift_reportFatalErrorsToDebugger = stashed_swift_reportFatalErrorsToDebugger
#endif
}
}
#endif
@@ -61,7 +72,7 @@ func isXCTestAvailable() -> Bool {
#endif
}
private func recordFailure(_ message: String, location: SourceLocation) {
public func recordFailure(_ message: String, location: SourceLocation) {
#if SWIFT_PACKAGE
XCTFail("\(message)", file: location.file, line: location.line)
#else
+1 -1
View File
@@ -111,6 +111,6 @@ internal func blockedRunLoopErrorMessageFor(_ fnName: String, leeway: TimeInterv
///
/// This function manages the main run loop (`NSRunLoop.mainRunLoop()`) while this function
/// is executing. Any attempts to touch the run loop may cause non-deterministic behavior.
public func waitUntil(timeout: TimeInterval = 1, file: FileString = #file, line: UInt = #line, action: @escaping (@escaping () -> Void) -> Void) {
public func waitUntil(timeout: TimeInterval = AsyncDefaults.Timeout, file: FileString = #file, line: UInt = #line, action: @escaping (@escaping () -> Void) -> Void) {
NMBWait.until(timeout: timeout, file: file, line: line, action: action)
}
+14 -21
View File
@@ -1,23 +1,5 @@
import Foundation
// Deprecated
internal func expressionMatches<T, U>(_ expression: Expression<T>, matcher: U, to: String, description: String?) -> (Bool, FailureMessage)
where U: Matcher, U.ValueType == T {
let msg = FailureMessage()
msg.userDescription = description
msg.to = to
do {
let pass = try matcher.matches(expression, failureMessage: msg)
if msg.actualValue == "" {
msg.actualValue = "<\(stringify(try expression.evaluate()))>"
}
return (pass, msg)
} catch let error {
msg.stringValue = "unexpected error thrown: <\(error)>"
return (false, msg)
}
}
// Deprecated
internal func expressionDoesNotMatch<T, U>(_ expression: Expression<T>, matcher: U, toNot: String, description: String?) -> (Bool, FailureMessage)
where U: Matcher, U.ValueType == T {
@@ -75,6 +57,10 @@ public struct Expectation<T> {
public let expression: Expression<T>
public init(expression: Expression<T>) {
self.expression = expression
}
public func verify(_ pass: Bool, _ message: FailureMessage) {
let handler = NimbleEnvironment.activeInstance.assertionHandler
handler.assert(pass, message: message, location: expression.location)
@@ -85,8 +71,15 @@ public struct Expectation<T> {
/// DEPRECATED: Tests the actual value using a matcher to match.
public func to<U>(_ matcher: U, description: String? = nil)
where U: Matcher, U.ValueType == T {
let (pass, msg) = expressionMatches(expression, matcher: matcher, to: "to", description: description)
verify(pass, msg)
let (pass, msg) = execute(
expression,
.toMatch,
matcher.predicate,
to: "to",
description: description,
captureExceptions: false
)
verify(pass, msg)
}
/// DEPRECATED: Tests the actual value using a matcher to not match.
@@ -127,6 +120,6 @@ public struct Expectation<T> {
}
// see:
// - AsyncMatcherWrapper for extension
// - `async` for extension
// - NMBExpectation for Objective-C interface
}
+3 -1
View File
@@ -152,8 +152,10 @@ public indirect enum ExpectationMessage {
// Backwards compatibility: converts ExpectationMessage tree to FailureMessage
internal func update(failureMessage: FailureMessage) {
switch self {
case let .fail(msg):
case let .fail(msg) where !msg.isEmpty:
failureMessage.stringValue = msg
case .fail:
break
case let .expectedTo(msg):
failureMessage.actualValue = nil
failureMessage.postfixMessage = msg
+3 -3
View File
@@ -68,7 +68,7 @@ extension NMBObjCMatcher {
@objc public class func allPassMatcher(_ matcher: NMBMatcher) -> NMBPredicate {
return NMBPredicate { actualExpression in
let location = actualExpression.location
let actualValue = try! actualExpression.evaluate()
let actualValue = try actualExpression.evaluate()
var nsObjects = [NSObject]()
var collectionIsUsable = true
@@ -99,7 +99,7 @@ extension NMBObjCMatcher {
let expr = Expression(expression: ({ nsObjects }), location: location)
let pred: Predicate<[NSObject]> = createPredicate(Predicate { expr in
if let predicate = matcher as? NMBPredicate {
return predicate.satisfies(({ try! expr.evaluate() }), location: expr.location).toSwift()
return predicate.satisfies(({ try expr.evaluate() }), location: expr.location).toSwift()
} else {
let failureMessage = FailureMessage()
let result = matcher.matches(
@@ -114,7 +114,7 @@ extension NMBObjCMatcher {
)
}
})
return try! pred.satisfies(expr).toObjectiveC()
return try pred.satisfies(expr).toObjectiveC()
}
}
}
@@ -32,76 +32,7 @@ private func async<T>(style: ExpectationStyle, predicate: Predicate<T>, timeout:
// swiftlint:disable:next line_length
return PredicateResult(status: .fail, message: lastPredicateResult!.message.appended(message: " (timed out, but main thread was unresponsive)."))
case .incomplete:
internalError("Reached .incomplete state for toEventually(...).")
}
}
}
// Deprecated
internal struct AsyncMatcherWrapper<T, U>: Matcher
where U: Matcher, U.ValueType == T {
let fullMatcher: U
let timeoutInterval: TimeInterval
let pollInterval: TimeInterval
init(fullMatcher: U, timeoutInterval: TimeInterval = AsyncDefaults.Timeout, pollInterval: TimeInterval = AsyncDefaults.PollInterval) {
self.fullMatcher = fullMatcher
self.timeoutInterval = timeoutInterval
self.pollInterval = pollInterval
}
func matches(_ actualExpression: Expression<T>, failureMessage: FailureMessage) -> Bool {
let uncachedExpression = actualExpression.withoutCaching()
let fnName = "expect(...).toEventually(...)"
let result = pollBlock(
pollInterval: pollInterval,
timeoutInterval: timeoutInterval,
file: actualExpression.location.file,
line: actualExpression.location.line,
fnName: fnName) {
try self.fullMatcher.matches(uncachedExpression, failureMessage: failureMessage)
}
switch result {
case let .completed(isSuccessful): return isSuccessful
case .timedOut: return false
case let .errorThrown(error):
failureMessage.stringValue = "an unexpected error thrown: <\(error)>"
return false
case let .raisedException(exception):
failureMessage.stringValue = "an unexpected exception thrown: <\(exception)>"
return false
case .blockedRunLoop:
failureMessage.postfixMessage += " (timed out, but main thread was unresponsive)."
return false
case .incomplete:
internalError("Reached .incomplete state for toEventually(...).")
}
}
func doesNotMatch(_ actualExpression: Expression<T>, failureMessage: FailureMessage) -> Bool {
let uncachedExpression = actualExpression.withoutCaching()
let result = pollBlock(
pollInterval: pollInterval,
timeoutInterval: timeoutInterval,
file: actualExpression.location.file,
line: actualExpression.location.line,
fnName: "expect(...).toEventuallyNot(...)") {
try self.fullMatcher.doesNotMatch(uncachedExpression, failureMessage: failureMessage)
}
switch result {
case let .completed(isSuccessful): return isSuccessful
case .timedOut: return false
case let .errorThrown(error):
failureMessage.stringValue = "an unexpected error thrown: <\(error)>"
return false
case let .raisedException(exception):
failureMessage.stringValue = "an unexpected exception thrown: <\(exception)>"
return false
case .blockedRunLoop:
failureMessage.postfixMessage += " (timed out, but main thread was unresponsive)."
return false
case .incomplete:
internalError("Reached .incomplete state for toEventuallyNot(...).")
internalError("Reached .incomplete state for \(fnName)(...).")
}
}
}
@@ -182,14 +113,19 @@ extension Expectation {
public func toEventually<U>(_ matcher: U, timeout: TimeInterval = AsyncDefaults.Timeout, pollInterval: TimeInterval = AsyncDefaults.PollInterval, description: String? = nil)
where U: Matcher, U.ValueType == T {
if expression.isClosure {
let (pass, msg) = expressionMatches(
let (pass, msg) = execute(
expression,
matcher: AsyncMatcherWrapper(
fullMatcher: matcher,
timeoutInterval: timeout,
pollInterval: pollInterval),
.toMatch,
async(
style: .toMatch,
predicate: matcher.predicate,
timeout: timeout,
poll: pollInterval,
fnName: "toEventually"
),
to: "to eventually",
description: description
description: description,
captureExceptions: false
)
verify(pass, msg)
} else {
@@ -208,10 +144,13 @@ extension Expectation {
if expression.isClosure {
let (pass, msg) = expressionDoesNotMatch(
expression,
matcher: AsyncMatcherWrapper(
fullMatcher: matcher,
timeoutInterval: timeout,
pollInterval: pollInterval),
matcher: async(
style: .toNotMatch,
predicate: matcher.predicate,
timeout: timeout,
poll: pollInterval,
fnName: "toEventuallyNot"
),
toNot: "to eventually not",
description: description
)
+2 -2
View File
@@ -59,8 +59,8 @@ public func beAKindOf(_ expectedClass: AnyClass) -> Predicate<NSObject> {
extension NMBObjCMatcher {
@objc public class func beAKindOfMatcher(_ expected: AnyClass) -> NMBMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
return try! beAKindOf(expected).matches(actualExpression, failureMessage: failureMessage)
return NMBPredicate { actualExpression in
return try beAKindOf(expected).satisfies(actualExpression).toObjectiveC()
}
}
}
@@ -48,8 +48,8 @@ public func beAnInstanceOf(_ expectedClass: AnyClass) -> Predicate<NSObject> {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
extension NMBObjCMatcher {
@objc public class func beAnInstanceOfMatcher(_ expected: AnyClass) -> NMBMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
return try! beAnInstanceOf(expected).matches(actualExpression, failureMessage: failureMessage)
return NMBPredicate { actualExpression in
return try beAnInstanceOf(expected).satisfies(actualExpression).toObjectiveC()
}
}
}
+16 -4
View File
@@ -43,22 +43,34 @@ public class NMBObjCBeCloseToMatcher: NSObject, NMBMatcher {
_delta = within
}
@objc public func matches(_ actualExpression: @escaping () -> NSObject!, failureMessage: FailureMessage, location: SourceLocation) -> Bool {
@objc public func matches(_ actualExpression: @escaping () -> NSObject?, failureMessage: FailureMessage, location: SourceLocation) -> Bool {
let actualBlock: () -> NMBDoubleConvertible? = ({
return actualExpression() as? NMBDoubleConvertible
})
let expr = Expression(expression: actualBlock, location: location)
let matcher = beCloseTo(self._expected, within: self._delta)
return try! matcher.matches(expr, failureMessage: failureMessage)
do {
return try matcher.matches(expr, failureMessage: failureMessage)
} catch let error {
failureMessage.stringValue = "unexpected error thrown: <\(error)>"
return false
}
}
@objc public func doesNotMatch(_ actualExpression: @escaping () -> NSObject!, failureMessage: FailureMessage, location: SourceLocation) -> Bool {
@objc public func doesNotMatch(_ actualExpression: @escaping () -> NSObject?, failureMessage: FailureMessage, location: SourceLocation) -> Bool {
let actualBlock: () -> NMBDoubleConvertible? = ({
return actualExpression() as? NMBDoubleConvertible
})
let expr = Expression(expression: actualBlock, location: location)
let matcher = beCloseTo(self._expected, within: self._delta)
return try! matcher.doesNotMatch(expr, failureMessage: failureMessage)
do {
return try matcher.doesNotMatch(expr, failureMessage: failureMessage)
} catch let error {
failureMessage.stringValue = "unexpected error thrown: <\(error)>"
return false
}
}
@objc public var within: (CDouble) -> NMBObjCBeCloseToMatcher {
+3 -3
View File
@@ -66,14 +66,14 @@ extension NMBObjCMatcher {
@objc public class func beEmptyMatcher() -> NMBPredicate {
return NMBPredicate { actualExpression in
let location = actualExpression.location
let actualValue = try! actualExpression.evaluate()
let actualValue = try actualExpression.evaluate()
if let value = actualValue as? NMBCollection {
let expr = Expression(expression: ({ value as NMBCollection }), location: location)
return try! beEmpty().satisfies(expr).toObjectiveC()
return try beEmpty().satisfies(expr).toObjectiveC()
} else if let value = actualValue as? NSString {
let expr = Expression(expression: ({ value as String }), location: location)
return try! beEmpty().satisfies(expr).toObjectiveC()
return try beEmpty().satisfies(expr).toObjectiveC()
} else if let actualValue = actualValue {
// swiftlint:disable:next line_length
let badTypeErrorMsg = "be empty (only works for NSArrays, NSSets, NSIndexSets, NSDictionaries, NSHashTables, and NSStrings)"
@@ -13,13 +13,13 @@ public func beGreaterThan<T: Comparable>(_ expectedValue: T?) -> Predicate<T> {
/// A Nimble matcher that succeeds when the actual value is greater than the expected value.
public func beGreaterThan(_ expectedValue: NMBComparable?) -> Predicate<NMBComparable> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
failureMessage.postfixMessage = "be greater than <\(stringify(expectedValue))>"
let errorMessage = "be greater than <\(stringify(expectedValue))>"
return Predicate.simple(errorMessage) { actualExpression in
let actualValue = try actualExpression.evaluate()
let matches = actualValue != nil
&& actualValue!.NMB_compare(expectedValue) == ComparisonResult.orderedDescending
return matches
}.requireNonNil
return PredicateStatus(bool: matches)
}
}
public func ><T: Comparable>(lhs: Expectation<T>, rhs: T) {
@@ -35,7 +35,7 @@ extension NMBObjCMatcher {
@objc public class func beGreaterThanMatcher(_ expected: NMBComparable?) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
let expr = actualExpression.cast { $0 as? NMBComparable }
return try! beGreaterThan(expected).matches(expr, failureMessage: failureMessage)
return try beGreaterThan(expected).matches(expr, failureMessage: failureMessage)
}
}
}
@@ -3,25 +3,25 @@ import Foundation
/// A Nimble matcher that succeeds when the actual value is greater than
/// or equal to the expected value.
public func beGreaterThanOrEqualTo<T: Comparable>(_ expectedValue: T?) -> Predicate<T> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
failureMessage.postfixMessage = "be greater than or equal to <\(stringify(expectedValue))>"
let message = "be greater than or equal to <\(stringify(expectedValue))>"
return Predicate.simple(message) { actualExpression in
let actualValue = try actualExpression.evaluate()
if let actual = actualValue, let expected = expectedValue {
return actual >= expected
return PredicateStatus(bool: actual >= expected)
}
return false
}.requireNonNil
return .fail
}
}
/// A Nimble matcher that succeeds when the actual value is greater than
/// or equal to the expected value.
public func beGreaterThanOrEqualTo<T: NMBComparable>(_ expectedValue: T?) -> Predicate<T> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
failureMessage.postfixMessage = "be greater than or equal to <\(stringify(expectedValue))>"
let message = "be greater than or equal to <\(stringify(expectedValue))>"
return Predicate.simple(message) { actualExpression in
let actualValue = try actualExpression.evaluate()
let matches = actualValue != nil && actualValue!.NMB_compare(expectedValue) != ComparisonResult.orderedAscending
return matches
}.requireNonNil
return PredicateStatus(bool: matches)
}
}
public func >=<T: Comparable>(lhs: Expectation<T>, rhs: T) {
@@ -37,7 +37,7 @@ extension NMBObjCMatcher {
@objc public class func beGreaterThanOrEqualToMatcher(_ expected: NMBComparable?) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
let expr = actualExpression.cast { $0 as? NMBComparable }
return try! beGreaterThanOrEqualTo(expected).matches(expr, failureMessage: failureMessage)
return try beGreaterThanOrEqualTo(expected).matches(expr, failureMessage: failureMessage)
}
}
}
@@ -3,20 +3,27 @@ import Foundation
/// A Nimble matcher that succeeds when the actual value is the same instance
/// as the expected instance.
public func beIdenticalTo(_ expected: Any?) -> Predicate<Any> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
return Predicate.define { actualExpression in
#if os(Linux)
let actual = try actualExpression.evaluate() as? AnyObject
#else
let actual = try actualExpression.evaluate() as AnyObject?
#endif
failureMessage.actualValue = "\(identityAsString(actual))"
failureMessage.postfixMessage = "be identical to \(identityAsString(expected))"
let bool: Bool
#if os(Linux)
return actual === (expected as? AnyObject) && actual !== nil
bool = actual === (expected as? AnyObject) && actual !== nil
#else
return actual === (expected as AnyObject?) && actual !== nil
bool = actual === (expected as AnyObject?) && actual !== nil
#endif
}.requireNonNil
return PredicateResult(
bool: bool,
message: .expectedCustomValueTo(
"be identical to \(identityAsString(expected))",
"\(identityAsString(actual))"
)
)
}
}
public func === (lhs: Expectation<Any>, rhs: Any?) {
@@ -39,7 +46,7 @@ extension NMBObjCMatcher {
@objc public class func beIdenticalToMatcher(_ expected: NSObject?) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
let aExpr = actualExpression.cast { $0 as Any? }
return try! beIdenticalTo(expected).matches(aExpr, failureMessage: failureMessage)
return try beIdenticalTo(expected).matches(aExpr, failureMessage: failureMessage)
}
}
}
+10 -10
View File
@@ -2,23 +2,23 @@ import Foundation
/// A Nimble matcher that succeeds when the actual value is less than the expected value.
public func beLessThan<T: Comparable>(_ expectedValue: T?) -> Predicate<T> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
failureMessage.postfixMessage = "be less than <\(stringify(expectedValue))>"
let message = "be less than <\(stringify(expectedValue))>"
return Predicate.simple(message) { actualExpression in
if let actual = try actualExpression.evaluate(), let expected = expectedValue {
return actual < expected
return PredicateStatus(bool: actual < expected)
}
return false
}.requireNonNil
return .fail
}
}
/// A Nimble matcher that succeeds when the actual value is less than the expected value.
public func beLessThan(_ expectedValue: NMBComparable?) -> Predicate<NMBComparable> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
failureMessage.postfixMessage = "be less than <\(stringify(expectedValue))>"
let message = "be less than <\(stringify(expectedValue))>"
return Predicate.simple(message) { actualExpression in
let actualValue = try actualExpression.evaluate()
let matches = actualValue != nil && actualValue!.NMB_compare(expectedValue) == ComparisonResult.orderedAscending
return matches
}.requireNonNil
return PredicateStatus(bool: matches)
}
}
public func <<T: Comparable>(lhs: Expectation<T>, rhs: T) {
@@ -34,7 +34,7 @@ extension NMBObjCMatcher {
@objc public class func beLessThanMatcher(_ expected: NMBComparable?) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
let expr = actualExpression.cast { $0 as? NMBComparable }
return try! beLessThan(expected).matches(expr, failureMessage: failureMessage)
return try beLessThan(expected).matches(expr, failureMessage: failureMessage)
}
}
}
@@ -3,23 +3,22 @@ import Foundation
/// A Nimble matcher that succeeds when the actual value is less than
/// or equal to the expected value.
public func beLessThanOrEqualTo<T: Comparable>(_ expectedValue: T?) -> Predicate<T> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
failureMessage.postfixMessage = "be less than or equal to <\(stringify(expectedValue))>"
return Predicate.simple("be less than or equal to <\(stringify(expectedValue))>") { actualExpression in
if let actual = try actualExpression.evaluate(), let expected = expectedValue {
return actual <= expected
return PredicateStatus(bool: actual <= expected)
}
return false
}.requireNonNil
return .fail
}
}
/// A Nimble matcher that succeeds when the actual value is less than
/// or equal to the expected value.
public func beLessThanOrEqualTo<T: NMBComparable>(_ expectedValue: T?) -> Predicate<T> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
failureMessage.postfixMessage = "be less than or equal to <\(stringify(expectedValue))>"
return Predicate.simple("be less than or equal to <\(stringify(expectedValue))>") { actualExpression in
let actualValue = try actualExpression.evaluate()
return actualValue != nil && actualValue!.NMB_compare(expectedValue) != ComparisonResult.orderedDescending
}.requireNonNil
let matches = actualValue.map { $0.NMB_compare(expectedValue) != .orderedDescending } ?? false
return PredicateStatus(bool: matches)
}
}
public func <=<T: Comparable>(lhs: Expectation<T>, rhs: T) {
@@ -35,7 +34,7 @@ extension NMBObjCMatcher {
@objc public class func beLessThanOrEqualToMatcher(_ expected: NMBComparable?) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
let expr = actualExpression.cast { $0 as? NMBComparable }
return try! beLessThanOrEqualTo(expected).matches(expr, failureMessage: failureMessage)
return try beLessThanOrEqualTo(expected).matches(expr, failureMessage: failureMessage)
}
}
}
+4 -4
View File
@@ -139,28 +139,28 @@ extension NMBObjCMatcher {
@objc public class func beTruthyMatcher() -> NMBObjCMatcher {
return NMBObjCMatcher { actualExpression, failureMessage in
let expr = actualExpression.cast { ($0 as? NSNumber)?.boolValue ?? false }
return try! beTruthy().matches(expr, failureMessage: failureMessage)
return try beTruthy().matches(expr, failureMessage: failureMessage)
}
}
@objc public class func beFalsyMatcher() -> NMBObjCMatcher {
return NMBObjCMatcher { actualExpression, failureMessage in
let expr = actualExpression.cast { ($0 as? NSNumber)?.boolValue ?? false }
return try! beFalsy().matches(expr, failureMessage: failureMessage)
return try beFalsy().matches(expr, failureMessage: failureMessage)
}
}
@objc public class func beTrueMatcher() -> NMBObjCMatcher {
return NMBObjCMatcher { actualExpression, failureMessage in
let expr = actualExpression.cast { ($0 as? NSNumber)?.boolValue ?? false }
return try! beTrue().matches(expr, failureMessage: failureMessage)
return try beTrue().matches(expr, failureMessage: failureMessage)
}
}
@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)
return try beFalse().matches(expr, failureMessage: failureMessage)
}
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ public func beNil<T>() -> Predicate<T> {
extension NMBObjCMatcher {
@objc public class func beNilMatcher() -> NMBObjCMatcher {
return NMBObjCMatcher { actualExpression, failureMessage in
return try! beNil().matches(actualExpression, failureMessage: failureMessage)
return try beNil().matches(actualExpression, failureMessage: failureMessage)
}
}
}
+2 -3
View File
@@ -2,10 +2,9 @@ import Foundation
/// A Nimble matcher that succeeds when the actual value is Void.
public func beVoid() -> Predicate<()> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
failureMessage.postfixMessage = "be void"
return Predicate.simpleNilable("be void") { actualExpression in
let actualValue: ()? = try actualExpression.evaluate()
return actualValue != nil
return PredicateStatus(bool: actualValue != nil)
}
}
+3 -3
View File
@@ -46,13 +46,13 @@ public func beginWith(_ startingSubstring: String) -> Predicate<String> {
extension NMBObjCMatcher {
@objc public class func beginWithMatcher(_ expected: Any) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
let actual = try! actualExpression.evaluate()
let actual = try actualExpression.evaluate()
if (actual as? String) != nil {
let expr = actualExpression.cast { $0 as? String }
return try! beginWith(expected as! String).matches(expr, failureMessage: failureMessage)
return try beginWith(expected as! String).matches(expr, failureMessage: failureMessage)
} else {
let expr = actualExpression.cast { $0 as? NMBOrderedCollection }
return try! beginWith(expected).matches(expr, failureMessage: failureMessage)
return try beginWith(expected).matches(expr, failureMessage: failureMessage)
}
}
}
+24 -24
View File
@@ -8,15 +8,15 @@ public func contain<S: Sequence, T: Equatable>(_ items: T...) -> Predicate<S>
public func contain<S: Sequence, T: Equatable>(_ items: [T]) -> Predicate<S>
where S.Iterator.Element == T {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
failureMessage.postfixMessage = "contain <\(arrayAsString(items))>"
return Predicate.simple("contain <\(arrayAsString(items))>") { actualExpression in
if let actual = try actualExpression.evaluate() {
return items.all {
let matches = items.all {
return actual.contains($0)
}
return PredicateStatus(bool: matches)
}
return false
}.requireNonNil
return .fail
}
}
/// A Nimble matcher that succeeds when the actual string contains the expected substring.
@@ -25,16 +25,16 @@ public func contain(_ substrings: String...) -> Predicate<String> {
}
public func contain(_ substrings: [String]) -> Predicate<String> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
failureMessage.postfixMessage = "contain <\(arrayAsString(substrings))>"
return Predicate.simple("contain <\(arrayAsString(substrings))>") { actualExpression in
if let actual = try actualExpression.evaluate() {
return substrings.all {
let matches = substrings.all {
let range = actual.range(of: $0)
return range != nil && !range!.isEmpty
}
return PredicateStatus(bool: matches)
}
return false
}.requireNonNil
return .fail
}
}
/// A Nimble matcher that succeeds when the actual string contains the expected substring.
@@ -43,13 +43,13 @@ public func contain(_ substrings: NSString...) -> Predicate<NSString> {
}
public func contain(_ substrings: [NSString]) -> Predicate<NSString> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
failureMessage.postfixMessage = "contain <\(arrayAsString(substrings))>"
return Predicate.simple("contain <\(arrayAsString(substrings))>") { actualExpression in
if let actual = try actualExpression.evaluate() {
return substrings.all { actual.range(of: $0.description).length != 0 }
let matches = substrings.all { actual.range(of: $0.description).length != 0 }
return PredicateStatus(bool: matches)
}
return false
}.requireNonNil
return .fail
}
}
/// A Nimble matcher that succeeds when the actual collection contains the expected object.
@@ -58,13 +58,13 @@ public func contain(_ items: Any?...) -> Predicate<NMBContainer> {
}
public func contain(_ items: [Any?]) -> Predicate<NMBContainer> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
failureMessage.postfixMessage = "contain <\(arrayAsString(items))>"
guard let actual = try actualExpression.evaluate() else { return false }
return items.all { item in
return item != nil && actual.contains(item!)
return Predicate.simple("contain <\(arrayAsString(items))>") { actualExpression in
guard let actual = try actualExpression.evaluate() else { return .fail }
let matches = items.all { item in
return item.map { actual.contains($0) } ?? false
}
}.requireNonNil
return PredicateStatus(bool: matches)
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
@@ -72,16 +72,16 @@ extension NMBObjCMatcher {
@objc public class func containMatcher(_ expected: [NSObject]) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
let location = actualExpression.location
let actualValue = try! actualExpression.evaluate()
let actualValue = try actualExpression.evaluate()
if let value = actualValue as? NMBContainer {
let expr = Expression(expression: ({ value as NMBContainer }), location: location)
// 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).matches(expr, failureMessage: failureMessage)
} 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)
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)"
@@ -2,34 +2,33 @@ import Foundation
public func containElementSatisfying<S: Sequence, T>(_ predicate: @escaping ((T) -> Bool), _ predicateDescription: String = "") -> Predicate<S> where S.Iterator.Element == T {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
failureMessage.actualValue = nil
return Predicate.define { actualExpression in
let message: ExpectationMessage
if predicateDescription == "" {
failureMessage.postfixMessage = "find object in collection that satisfies predicate"
message = .expectedTo("find object in collection that satisfies predicate")
} else {
failureMessage.postfixMessage = "find object in collection \(predicateDescription)"
message = .expectedTo("find object in collection \(predicateDescription)")
}
if let sequence = try actualExpression.evaluate() {
for object in sequence {
if predicate(object) {
return true
return PredicateResult(bool: true, message: message)
}
}
return false
return PredicateResult(bool: false, message: message)
}
return false
}.requireNonNil
return PredicateResult(status: .fail, message: message)
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
extension NMBObjCMatcher {
@objc public class func containElementSatisfyingMatcher(_ predicate: @escaping ((NSObject) -> Bool)) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
let value = try! actualExpression.evaluate()
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"
@@ -51,8 +50,7 @@ public func containElementSatisfying<S: Sequence, T>(_ predicate: @escaping ((T)
}
failureMessage.actualValue = nil
failureMessage.postfixMessage = ""
failureMessage.to = "to find object in collection that satisfies predicate"
failureMessage.postfixMessage = "find object in collection that satisfies predicate"
return false
}
}
+17 -21
View File
@@ -4,9 +4,7 @@ import Foundation
/// is equal to the expected value.
public func endWith<S: Sequence, T: Equatable>(_ endingElement: T) -> Predicate<S>
where S.Iterator.Element == T {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
failureMessage.postfixMessage = "end with <\(endingElement)>"
return Predicate.simple("end with <\(endingElement)>") { actualExpression in
if let actualValue = try actualExpression.evaluate() {
var actualGenerator = actualValue.makeIterator()
var lastItem: T?
@@ -16,55 +14,53 @@ public func endWith<S: Sequence, T: Equatable>(_ endingElement: T) -> Predicate<
item = actualGenerator.next()
} while(item != nil)
return lastItem == endingElement
return PredicateStatus(bool: lastItem == endingElement)
}
return false
}.requireNonNil
return .fail
}
}
/// A Nimble matcher that succeeds when the actual collection's last element
/// is equal to the expected object.
public func endWith(_ endingElement: Any) -> Predicate<NMBOrderedCollection> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
failureMessage.postfixMessage = "end with <\(endingElement)>"
guard let collection = try actualExpression.evaluate() else { return false }
guard collection.count > 0 else { return false }
return Predicate.simple("end with <\(endingElement)>") { actualExpression in
guard let collection = try actualExpression.evaluate() else { return .fail }
guard collection.count > 0 else { return PredicateStatus(bool: false) }
#if os(Linux)
guard let collectionValue = collection.object(at: collection.count - 1) as? NSObject else {
return false
return .fail
}
#else
let collectionValue = collection.object(at: collection.count - 1) as AnyObject
#endif
return collectionValue.isEqual(endingElement)
}.requireNonNil
return PredicateStatus(bool: collectionValue.isEqual(endingElement))
}
}
/// A Nimble matcher that succeeds when the actual string contains the expected substring
/// where the expected substring's location is the actual string's length minus the
/// expected substring's length.
public func endWith(_ endingSubstring: String) -> Predicate<String> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
failureMessage.postfixMessage = "end with <\(endingSubstring)>"
return Predicate.simple("end with <\(endingSubstring)>") { actualExpression in
if let collection = try actualExpression.evaluate() {
return collection.hasSuffix(endingSubstring)
return PredicateStatus(bool: collection.hasSuffix(endingSubstring))
}
return false
}.requireNonNil
return .fail
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
extension NMBObjCMatcher {
@objc public class func endWithMatcher(_ expected: Any) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
let actual = try! actualExpression.evaluate()
let actual = try actualExpression.evaluate()
if (actual as? String) != nil {
let expr = actualExpression.cast { $0 as? String }
return try! endWith(expected as! String).matches(expr, failureMessage: failureMessage)
return try endWith(expected as! String).matches(expr, failureMessage: failureMessage)
} else {
let expr = actualExpression.cast { $0 as? NMBOrderedCollection }
return try! endWith(expected).matches(expr, failureMessage: failureMessage)
return try endWith(expected).matches(expr, failureMessage: failureMessage)
}
}
}
+2 -2
View File
@@ -212,8 +212,8 @@ public func !=<T, C: Equatable>(lhs: Expectation<[T: C]>, rhs: [T: C]?) {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
extension NMBObjCMatcher {
@objc public class func equalMatcher(_ expected: NSObject) -> NMBMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
return try! equal(expected).matches(actualExpression, failureMessage: failureMessage)
return NMBPredicate { actualExpression in
return try equal(expected).satisfies(actualExpression).toObjectiveC()
}
}
}
+23 -17
View File
@@ -8,33 +8,39 @@ 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> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
return Predicate.define { actualExpression in
if let actualValue = try actualExpression.evaluate() {
// swiftlint:disable:next line_length
failureMessage.postfixMessage = "have \(prettyCollectionType(actualValue)) with count \(stringify(expectedValue))"
let message = ExpectationMessage
.expectedCustomValueTo(
"have \(prettyCollectionType(actualValue)) with count \(stringify(expectedValue))",
"\(actualValue.count)"
)
.appended(details: "Actual Value: \(stringify(actualValue))")
let result = expectedValue == actualValue.count
failureMessage.actualValue = "\(actualValue.count)"
failureMessage.extendedMessage = "Actual Value: \(stringify(actualValue))"
return result
return PredicateResult(bool: result, message: message)
} else {
return false
return PredicateResult(status: .fail, message: .fail(""))
}
}.requireNonNil
}
}
/// A Nimble matcher that succeeds when the actual collection's count equals
/// the expected value
public func haveCount(_ expectedValue: Int) -> Predicate<NMBCollection> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
return Predicate { actualExpression in
if let actualValue = try actualExpression.evaluate() {
// swiftlint:disable:next line_length
failureMessage.postfixMessage = "have \(prettyCollectionType(actualValue)) with count \(stringify(expectedValue))"
let message = ExpectationMessage
.expectedCustomValueTo(
"have \(prettyCollectionType(actualValue)) with count \(stringify(expectedValue))",
"\(actualValue.count)"
)
.appended(details: "Actual Value: \(stringify(actualValue))")
let result = expectedValue == actualValue.count
failureMessage.actualValue = "\(actualValue.count)"
failureMessage.extendedMessage = "Actual Value: \(stringify(actualValue))"
return result
return PredicateResult(bool: result, message: message)
} else {
return false
return PredicateResult(status: .fail, message: .fail(""))
}
}
}
@@ -44,10 +50,10 @@ extension NMBObjCMatcher {
@objc public class func haveCountMatcher(_ expected: NSNumber) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
let location = actualExpression.location
let actualValue = try! actualExpression.evaluate()
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)
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)))"
+7 -8
View File
@@ -3,26 +3,25 @@ import Foundation
/// A Nimble matcher that succeeds when the actual string satisfies the regular expression
/// described by the expected string.
public func match(_ expectedValue: String?) -> Predicate<String> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
failureMessage.postfixMessage = "match <\(stringify(expectedValue))>"
return Predicate.simple("match <\(stringify(expectedValue))>") { actualExpression in
if let actual = try actualExpression.evaluate() {
if let regexp = expectedValue {
return actual.range(of: regexp, options: .regularExpression) != nil
let bool = actual.range(of: regexp, options: .regularExpression) != nil
return PredicateStatus(bool: bool)
}
}
return false
}.requireNonNil
return .fail
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
extension NMBObjCMatcher {
@objc public class func matchMatcher(_ expected: NSString) -> NMBMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
return NMBPredicate { actualExpression in
let actual = actualExpression.cast { $0 as? String }
return try! match(expected.description).matches(actual, failureMessage: failureMessage)
return try match(expected.description).satisfies(actual).toObjectiveC()
}
}
}
+32 -14
View File
@@ -6,16 +6,24 @@ import Foundation
/// Errors are tried to be compared by their implementation of Equatable,
/// otherwise they fallback to comparison by _domain and _code.
public func matchError<T: Error>(_ error: T) -> Predicate<Error> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
let actualError: Error? = try actualExpression.evaluate()
return Predicate.define { actualExpression in
let actualError = try actualExpression.evaluate()
let failureMessage = FailureMessage()
setFailureMessageForError(
failureMessage,
postfixMessageVerb: "match",
actualError: actualError,
error: error
)
setFailureMessageForError(failureMessage, postfixMessageVerb: "match", actualError: actualError, error: error)
var matches = false
if let actualError = actualError, errorMatchesExpectedError(actualError, expectedError: error) {
matches = true
}
return matches
}.requireNonNil
return PredicateResult(bool: matches, message: failureMessage.toExpectationMessage())
}
}
/// A Nimble matcher that succeeds when the actual expression evaluates to an
@@ -24,35 +32,45 @@ public func matchError<T: Error>(_ error: T) -> Predicate<Error> {
/// Errors are tried to be compared by their implementation of Equatable,
/// otherwise they fallback to comparision by _domain and _code.
public func matchError<T: Error & Equatable>(_ error: T) -> Predicate<Error> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
let actualError: Error? = try actualExpression.evaluate()
return Predicate.define { actualExpression in
let actualError = try actualExpression.evaluate()
setFailureMessageForError(failureMessage, postfixMessageVerb: "match", actualError: actualError, error: error)
let failureMessage = FailureMessage()
setFailureMessageForError(
failureMessage,
postfixMessageVerb: "match",
actualError: actualError,
error: error
)
var matches = false
if let actualError = actualError as? T, error == actualError {
matches = true
}
return matches
}.requireNonNil
return PredicateResult(bool: matches, message: failureMessage.toExpectationMessage())
}
}
/// A Nimble matcher that succeeds when the actual expression evaluates to an
/// error of the specified type
public func matchError<T: Error>(_ errorType: T.Type) -> Predicate<Error> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
let actualError: Error? = try actualExpression.evaluate()
return Predicate.define { actualExpression in
let actualError = try actualExpression.evaluate()
let failureMessage = FailureMessage()
setFailureMessageForError(
failureMessage,
postfixMessageVerb: "match",
actualError: actualError,
errorType: errorType
)
var matches = false
if actualError as? T != nil {
matches = true
}
return matches
}.requireNonNil
return PredicateResult(bool: matches, message: failureMessage.toExpectationMessage())
}
}
@@ -31,8 +31,8 @@ extension Matcher {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
/// Objective-C interface to the Swift variant of Matcher.
@objc public protocol NMBMatcher {
func matches(_ actualBlock: @escaping () -> NSObject!, failureMessage: FailureMessage, location: SourceLocation) -> Bool
func doesNotMatch(_ actualBlock: @escaping () -> NSObject!, failureMessage: FailureMessage, location: SourceLocation) -> Bool
func matches(_ actualBlock: @escaping () -> NSObject?, failureMessage: FailureMessage, location: SourceLocation) -> Bool
func doesNotMatch(_ actualBlock: @escaping () -> NSObject?, failureMessage: FailureMessage, location: SourceLocation) -> Bool
}
#endif
@@ -74,7 +74,8 @@ public func postNotifications<T>(
let collector = NotificationCollector(notificationCenter: center)
collector.startObserving()
var once: Bool = false
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
return Predicate { actualExpression in
let collectorNotificationsExpression = Expression(memoizedExpression: { _ in
return collector.observedNotifications
}, location: actualExpression.location, withoutCaching: true)
@@ -85,12 +86,13 @@ public func postNotifications<T>(
_ = try actualExpression.evaluate()
}
let failureMessage = FailureMessage()
let match = try notificationsMatcher.matches(collectorNotificationsExpression, failureMessage: failureMessage)
if collector.observedNotifications.isEmpty {
failureMessage.actualValue = "no notifications"
} else {
failureMessage.actualValue = "<\(stringify(collector.observedNotifications))>"
}
return match
return PredicateResult(bool: match, message: failureMessage.toExpectationMessage())
}
}
+14 -10
View File
@@ -82,8 +82,8 @@ extension Predicate {
}
}
// Question: Should this be exposed? It's safer to not for now and decide later.
internal enum ExpectationStyle {
// The Expectation style intended for comparison to a PredicateStatus.
public enum ExpectationStyle {
case toMatch, toNotMatch
}
@@ -91,9 +91,9 @@ internal enum ExpectationStyle {
/// predicate.
public struct PredicateResult {
/// Status indicates if the predicate matches, does not match, or fails.
var status: PredicateStatus
public var status: PredicateStatus
/// The error message that can be displayed if it does not match
var message: ExpectationMessage
public var message: ExpectationMessage
/// Constructs a new PredicateResult with a given status and error message
public init(status: PredicateStatus, message: ExpectationMessage) {
@@ -108,7 +108,7 @@ public struct PredicateResult {
}
/// Converts the result to a boolean based on what the expectation intended
internal func toBoolean(expectation style: ExpectationStyle) -> Bool {
public func toBoolean(expectation style: ExpectationStyle) -> Bool {
return status.toBoolean(expectation: style)
}
}
@@ -242,7 +242,7 @@ extension Predicate {
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
public typealias PredicateBlock = (_ actualExpression: Expression<NSObject>) -> NMBPredicateResult
public typealias PredicateBlock = (_ actualExpression: Expression<NSObject>) throws -> NMBPredicateResult
public class NMBPredicate: NSObject {
private let predicate: PredicateBlock
@@ -251,20 +251,24 @@ public class NMBPredicate: NSObject {
self.predicate = predicate
}
func satisfies(_ expression: @escaping () -> NSObject!, location: SourceLocation) -> NMBPredicateResult {
func satisfies(_ expression: @escaping () throws -> NSObject?, location: SourceLocation) -> NMBPredicateResult {
let expr = Expression(expression: expression, location: location)
return self.predicate(expr)
do {
return try self.predicate(expr)
} catch let error {
return PredicateResult(status: .fail, message: .fail("unexpected error thrown: <\(error)>")).toObjectiveC()
}
}
}
extension NMBPredicate: NMBMatcher {
public func matches(_ actualBlock: @escaping () -> NSObject!, failureMessage: FailureMessage, location: SourceLocation) -> Bool {
public func matches(_ actualBlock: @escaping () -> NSObject?, failureMessage: FailureMessage, location: SourceLocation) -> Bool {
let result = satisfies(actualBlock, location: location).toSwift()
result.message.update(failureMessage: failureMessage)
return result.status.toBoolean(expectation: .toMatch)
}
public func doesNotMatch(_ actualBlock: @escaping () -> NSObject!, failureMessage: FailureMessage, location: SourceLocation) -> Bool {
public func doesNotMatch(_ actualBlock: @escaping () -> NSObject?, failureMessage: FailureMessage, location: SourceLocation) -> Bool {
let result = satisfies(actualBlock, location: location).toSwift()
result.message.update(failureMessage: failureMessage)
return result.status.toBoolean(expectation: .toNotMatch)
@@ -17,8 +17,7 @@ public func raiseException(
reason: String? = nil,
userInfo: NSDictionary? = nil,
closure: ((NSException) -> Void)? = nil) -> Predicate<Any> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
return Predicate { actualExpression in
var exception: NSException?
let capture = NMBExceptionCapture(handler: ({ e in
exception = e
@@ -26,9 +25,9 @@ public func raiseException(
capture.tryBlock {
_ = try! actualExpression.evaluate()
return
}
let failureMessage = FailureMessage()
setFailureMessageForException(
failureMessage,
exception: exception,
@@ -37,13 +36,15 @@ public func raiseException(
userInfo: userInfo,
closure: closure
)
return exceptionMatchesNonNilFieldsOrClosure(
let matches = exceptionMatchesNonNilFieldsOrClosure(
exception,
named: named,
reason: reason,
userInfo: userInfo,
closure: closure
)
return PredicateResult(bool: matches, message: failureMessage.toExpectationMessage())
}
}
@@ -129,19 +130,24 @@ public class NMBObjCRaiseExceptionMatcher: NSObject, NMBMatcher {
_block = block
}
@objc public func matches(_ actualBlock: @escaping () -> NSObject!, failureMessage: FailureMessage, location: SourceLocation) -> Bool {
@objc public func matches(_ actualBlock: @escaping () -> NSObject?, failureMessage: FailureMessage, location: SourceLocation) -> Bool {
let block: () -> Any? = ({ _ = actualBlock(); return nil })
let expr = Expression(expression: block, location: location)
return try! raiseException(
named: _name,
reason: _reason,
userInfo: _userInfo,
closure: _block
).matches(expr, failureMessage: failureMessage)
do {
return try raiseException(
named: _name,
reason: _reason,
userInfo: _userInfo,
closure: _block
).matches(expr, failureMessage: failureMessage)
} catch let error {
failureMessage.stringValue = "unexpected error thrown: <\(error)>"
return false
}
}
@objc public func doesNotMatch(_ actualBlock: @escaping () -> NSObject!, failureMessage: FailureMessage, location: SourceLocation) -> Bool {
@objc public func doesNotMatch(_ actualBlock: @escaping () -> NSObject?, failureMessage: FailureMessage, location: SourceLocation) -> Bool {
return !matches(actualBlock, failureMessage: failureMessage, location: location)
}
@@ -0,0 +1,76 @@
import Foundation
/// A Nimble matcher that succeeds when the actual value matches with all of the matchers
/// provided in the variable list of matchers.
public func satisfyAllOf<T, U>(_ matchers: U...) -> Predicate<T>
where U: Matcher, U.ValueType == T {
return satisfyAllOf(matchers.map { $0.predicate })
}
internal func satisfyAllOf<T>(_ predicates: [Predicate<T>]) -> Predicate<T> {
return Predicate.define { actualExpression in
var postfixMessages = [String]()
var matches = true
for predicate in predicates {
let result = try predicate.satisfies(actualExpression)
if result.toBoolean(expectation: .toNotMatch) {
matches = false
}
postfixMessages.append("{\(result.message.expectedMessage)}")
}
var msg: ExpectationMessage
if let actualValue = try actualExpression.evaluate() {
msg = .expectedCustomValueTo(
"match all of: " + postfixMessages.joined(separator: ", and "),
"\(actualValue)"
)
} else {
msg = .expectedActualValueTo(
"match all of: " + postfixMessages.joined(separator: ", and ")
)
}
return PredicateResult(bool: matches, message: msg)
}
}
public func && <T>(left: Predicate<T>, right: Predicate<T>) -> Predicate<T> {
return satisfyAllOf(left, right)
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
extension NMBObjCMatcher {
@objc public class func satisfyAllOfMatcher(_ matchers: [NMBMatcher]) -> NMBPredicate {
return NMBPredicate { actualExpression in
if matchers.isEmpty {
return NMBPredicateResult(
status: NMBPredicateStatus.fail,
message: NMBExpectationMessage(
fail: "satisfyAllOf must be called with at least one matcher"
)
)
}
var elementEvaluators = [Predicate<NSObject>]()
for matcher in matchers {
let elementEvaluator = Predicate<NSObject> { expression in
if let predicate = matcher as? NMBPredicate {
// swiftlint:disable:next line_length
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)
return PredicateResult(bool: success, message: failureMessage.toExpectationMessage())
}
}
elementEvaluators.append(elementEvaluator)
}
return try satisfyAllOf(elementEvaluators).satisfies(actualExpression).toObjectiveC()
}
}
}
#endif
@@ -4,33 +4,11 @@ import Foundation
/// provided in the variable list of matchers.
public func satisfyAnyOf<T, U>(_ matchers: U...) -> Predicate<T>
where U: Matcher, U.ValueType == T {
return satisfyAnyOf(matchers)
}
/// Deprecated. Please use `satisfyAnyOf<T>(_) -> Predicate<T>` instead.
internal func satisfyAnyOf<T, U>(_ matchers: [U]) -> Predicate<T>
where U: Matcher, U.ValueType == T {
return NonNilMatcherFunc<T> { actualExpression, failureMessage in
let postfixMessages = NSMutableArray()
var matches = false
for matcher in matchers {
if try matcher.matches(actualExpression, failureMessage: failureMessage) {
matches = true
}
postfixMessages.add(NSString(string: "{\(failureMessage.postfixMessage)}"))
}
failureMessage.postfixMessage = "match one of: " + postfixMessages.componentsJoined(by: ", or ")
if let actualValue = try actualExpression.evaluate() {
failureMessage.actualValue = "\(actualValue)"
}
return matches
}.predicate
return satisfyAnyOf(matchers.map { $0.predicate })
}
internal func satisfyAnyOf<T>(_ predicates: [Predicate<T>]) -> Predicate<T> {
return Predicate { actualExpression in
return Predicate.define { actualExpression in
var postfixMessages = [String]()
var matches = false
for predicate in predicates {
@@ -53,11 +31,8 @@ internal func satisfyAnyOf<T>(_ predicates: [Predicate<T>]) -> Predicate<T> {
)
}
return PredicateResult(
status: PredicateStatus(bool: matches),
message: msg
)
}.requireNonNil
return PredicateResult(bool: matches, message: msg)
}
}
public func || <T>(left: Predicate<T>, right: Predicate<T>) -> Predicate<T> {
@@ -90,7 +65,7 @@ extension NMBObjCMatcher {
let elementEvaluator = Predicate<NSObject> { expression in
if let predicate = matcher as? NMBPredicate {
// swiftlint:disable:next line_length
return predicate.satisfies({ try! expression.evaluate() }, location: actualExpression.location).toSwift()
return predicate.satisfies({ try expression.evaluate() }, location: actualExpression.location).toSwift()
} else {
let failureMessage = FailureMessage()
// swiftlint:disable:next line_length
@@ -102,7 +77,7 @@ extension NMBObjCMatcher {
elementEvaluators.append(elementEvaluator)
}
return try! satisfyAnyOf(elementEvaluators).satisfies(actualExpression).toObjectiveC()
return try satisfyAnyOf(elementEvaluators).satisfies(actualExpression).toObjectiveC()
}
}
}
@@ -1,13 +1,11 @@
import Foundation
public func throwAssertion() -> Predicate<Void> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
return Predicate { actualExpression in
#if arch(x86_64) && (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
failureMessage.postfixMessage = "throw an assertion"
failureMessage.actualValue = nil
var succeeded = true
let message = ExpectationMessage.expectedTo("throw an assertion")
var actualError: Error?
let caughtException: BadInstructionException? = catchBadInstruction {
#if os(tvOS)
if !NimbleEnvironment.activeInstance.suppressTVOSAssertionWarning {
@@ -27,21 +25,19 @@ public func throwAssertion() -> Predicate<Void> {
#endif
do {
try actualExpression.evaluate()
} catch let error {
succeeded = false
failureMessage.postfixMessage += "; threw error instead <\(error)>"
} catch {
actualError = error
}
}
if !succeeded {
return false
if let actualError = actualError {
return PredicateResult(
bool: false,
message: message.appended(message: "; threw error instead <\(actualError)>")
)
} else {
return PredicateResult(bool: caughtException != nil, message: message)
}
if caughtException == nil {
return false
}
return true
#elseif SWIFT_PACKAGE
fatalError("The throwAssertion Nimble matcher does not currently support Swift CLI." +
" You can silence this error by placing the test case inside an #if !SWIFT_PACKAGE" +
+45 -39
View File
@@ -12,22 +12,19 @@ import Foundation
/// nil arguments indicates that the matcher should not attempt to match against
/// that parameter.
public func throwError() -> Predicate<Any> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
return Predicate { actualExpression in
var actualError: Error?
do {
_ = try actualExpression.evaluate()
} catch let catchedError {
actualError = catchedError
} catch {
actualError = error
}
failureMessage.postfixMessage = "throw any error"
if let actualError = actualError {
failureMessage.actualValue = "<\(actualError)>"
return PredicateResult(bool: true, message: .expectedCustomValueTo("throw any error", "<\(actualError)>"))
} else {
failureMessage.actualValue = "no error"
return PredicateResult(bool: false, message: .expectedCustomValueTo("throw any error", "no error"))
}
return actualError != nil
}
}
@@ -43,15 +40,15 @@ public func throwError() -> Predicate<Any> {
/// nil arguments indicates that the matcher should not attempt to match against
/// that parameter.
public func throwError<T: Error>(_ error: T, closure: ((Error) -> Void)? = nil) -> Predicate<Any> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
return Predicate { actualExpression in
var actualError: Error?
do {
_ = try actualExpression.evaluate()
} catch let catchedError {
actualError = catchedError
} catch {
actualError = error
}
let failureMessage = FailureMessage()
setFailureMessageForError(
failureMessage,
actualError: actualError,
@@ -59,20 +56,23 @@ public func throwError<T: Error>(_ error: T, closure: ((Error) -> Void)? = nil)
errorType: nil,
closure: closure
)
var matches = false
if let actualError = actualError, errorMatchesExpectedError(actualError, expectedError: error) {
matches = true
if let closure = closure {
let assertions = gatherFailingExpectations {
closure(actualError)
}
let messages = assertions.map { $0.message }
if messages.count > 0 {
if !messages.isEmpty {
matches = false
}
}
}
return matches
return PredicateResult(bool: matches, message: failureMessage.toExpectationMessage())
}
}
@@ -88,15 +88,15 @@ public func throwError<T: Error>(_ error: T, closure: ((Error) -> Void)? = nil)
/// nil arguments indicates that the matcher should not attempt to match against
/// that parameter.
public func throwError<T: Error & Equatable>(_ error: T, closure: ((T) -> Void)? = nil) -> Predicate<Any> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
return Predicate { actualExpression in
var actualError: Error?
do {
_ = try actualExpression.evaluate()
} catch let catchedError {
actualError = catchedError
} catch {
actualError = error
}
let failureMessage = FailureMessage()
setFailureMessageForError(
failureMessage,
actualError: actualError,
@@ -104,6 +104,7 @@ public func throwError<T: Error & Equatable>(_ error: T, closure: ((T) -> Void)?
errorType: nil,
closure: closure
)
var matches = false
if let actualError = actualError as? T, error == actualError {
matches = true
@@ -113,12 +114,13 @@ public func throwError<T: Error & Equatable>(_ error: T, closure: ((T) -> Void)?
closure(actualError)
}
let messages = assertions.map { $0.message }
if messages.count > 0 {
if !messages.isEmpty {
matches = false
}
}
}
return matches
return PredicateResult(bool: matches, message: failureMessage.toExpectationMessage())
}
}
@@ -136,15 +138,15 @@ public func throwError<T: Error & Equatable>(_ error: T, closure: ((T) -> Void)?
public func throwError<T: Error>(
errorType: T.Type,
closure: ((T) -> Void)? = nil) -> Predicate<Any> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
return Predicate { actualExpression in
var actualError: Error?
do {
_ = try actualExpression.evaluate()
} catch let catchedError {
actualError = catchedError
} catch {
actualError = error
}
let failureMessage = FailureMessage()
setFailureMessageForError(
failureMessage,
actualError: actualError,
@@ -152,16 +154,18 @@ public func throwError<T: Error>(
errorType: errorType,
closure: closure
)
var matches = false
if let actualError = actualError {
matches = true
if let actualError = actualError as? T {
if let closure = closure {
let assertions = gatherFailingExpectations {
closure(actualError)
}
let messages = assertions.map { $0.message }
if messages.count > 0 {
if !messages.isEmpty {
matches = false
}
}
@@ -176,14 +180,14 @@ public func throwError<T: Error>(
}
}
let messages = assertions.map { $0.message }
if messages.count > 0 {
if !messages.isEmpty {
matches = false
}
}
}
}
return matches
return PredicateResult(bool: matches, message: failureMessage.toExpectationMessage())
}
}
@@ -195,15 +199,15 @@ public func throwError<T: Error>(
///
/// The closure only gets called when an error was thrown.
public func throwError(closure: @escaping ((Error) -> Void)) -> Predicate<Any> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
return Predicate { actualExpression in
var actualError: Error?
do {
_ = try actualExpression.evaluate()
} catch let catchedError {
actualError = catchedError
} catch {
actualError = error
}
let failureMessage = FailureMessage()
setFailureMessageForError(failureMessage, actualError: actualError, closure: closure)
var matches = false
@@ -214,11 +218,12 @@ public func throwError(closure: @escaping ((Error) -> Void)) -> Predicate<Any> {
closure(actualError)
}
let messages = assertions.map { $0.message }
if messages.count > 0 {
if !messages.isEmpty {
matches = false
}
}
return matches
return PredicateResult(bool: matches, message: failureMessage.toExpectationMessage())
}
}
@@ -230,15 +235,15 @@ public func throwError(closure: @escaping ((Error) -> Void)) -> Predicate<Any> {
///
/// The closure only gets called when an error was thrown.
public func throwError<T: Error>(closure: @escaping ((T) -> Void)) -> Predicate<Any> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
return Predicate { actualExpression in
var actualError: Error?
do {
_ = try actualExpression.evaluate()
} catch let catchedError {
actualError = catchedError
} catch {
actualError = error
}
let failureMessage = FailureMessage()
setFailureMessageForError(failureMessage, actualError: actualError, closure: closure)
var matches = false
@@ -249,10 +254,11 @@ public func throwError<T: Error>(closure: @escaping ((T) -> Void)) -> Predicate<
closure(actualError)
}
let messages = assertions.map { $0.message }
if messages.count > 0 {
if !messages.isEmpty {
matches = false
}
}
return matches
return PredicateResult(bool: matches, message: failureMessage.toExpectationMessage())
}
}
@@ -263,7 +263,11 @@ 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)
_ = RunLoop.current.run(mode: .default, before: .distantFuture)
#else
_ = RunLoop.current.run(mode: .defaultRunLoopMode, before: .distantFuture)
#endif
}
self.trigger.timeoutSource.cancel()
+3 -9
View File
@@ -144,7 +144,9 @@ extension Data: TestOutputStringConvertible {
/// will return the result of constructing a string from the value.
///
/// - SeeAlso: `TestOutputStringConvertible`
public func stringify<T>(_ value: T) -> String {
public func stringify<T>(_ value: T?) -> String {
guard let value = value else { return "nil" }
if let value = value as? TestOutputStringConvertible {
return value.testDescription
}
@@ -156,14 +158,6 @@ public func stringify<T>(_ value: T) -> String {
return String(describing: value)
}
/// -SeeAlso: `stringify<T>(value: T)`
public func stringify<T>(_ value: T?) -> String {
if let unboxed = value {
return stringify(unboxed)
}
return "nil"
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
@objc public class NMBStringer: NSObject {
@objc public class func stringify(_ obj: Any?) -> String {
@@ -1,9 +0,0 @@
#import <XCTest/XCTest.h>
#import <Nimble/Nimble-Swift.h>
SWIFT_CLASS("_TtC6Nimble22CurrentTestCaseTracker")
@interface CurrentTestCaseTracker : NSObject <XCTestObservation>
+ (CurrentTestCaseTracker *)sharedInstance;
@end
@interface CurrentTestCaseTracker (Register) @end
+6
View File
@@ -350,6 +350,12 @@ NIMBLE_EXPORT id<NMBMatcher> NMB_satisfyAnyOfWithMatchers(id matchers);
#define satisfyAnyOf(...) NMB_satisfyAnyOf(__VA_ARGS__)
#endif
NIMBLE_EXPORT id<NMBMatcher> NMB_satisfyAllOfWithMatchers(id matchers);
#define NMB_satisfyAllOf(...) NMB_satisfyAllOfWithMatchers(@[__VA_ARGS__])
#ifndef NIMBLE_DISABLE_SHORT_SYNTAX
#define satisfyAllOf(...) NMB_satisfyAllOf(__VA_ARGS__)
#endif
// In order to preserve breakpoint behavior despite using macros to fill in __FILE__ and __LINE__,
// define a builder that populates __FILE__ and __LINE__, and returns a block that takes timeout
// and action arguments. See https://github.com/Quick/Quick/pull/185 for details.
+9 -8
View File
@@ -1,13 +1,10 @@
#import <Nimble/DSL.h>
#if __has_include("Nimble-Swift.h")
#import "Nimble-Swift.h"
#else
#import <Nimble/Nimble-Swift.h>
SWIFT_CLASS("_TtC6Nimble7NMBWait")
@interface NMBWait : NSObject
+ (void)untilTimeout:(NSTimeInterval)timeout file:(NSString *)file line:(NSUInteger)line action:(void (^ _Nonnull)(void (^ _Nonnull)(void)))action;
+ (void)untilFile:(NSString *)file line:(NSUInteger)line action:(void (^ _Nonnull)(void (^ _Nonnull)(void)))action;
@end
#endif
NS_ASSUME_NONNULL_BEGIN
@@ -141,6 +138,10 @@ NIMBLE_EXPORT id<NMBMatcher> NMB_satisfyAnyOfWithMatchers(id matchers) {
return [NMBObjCMatcher satisfyAnyOfMatcher:matchers];
}
NIMBLE_EXPORT id<NMBMatcher> NMB_satisfyAllOfWithMatchers(id matchers) {
return [NMBObjCMatcher satisfyAllOfMatcher:matchers];
}
NIMBLE_EXPORT NMBObjCRaiseExceptionMatcher *NMB_raiseException() {
return [NMBObjCMatcher raiseExceptionMatcher];
}
@@ -16,7 +16,7 @@
return self;
}
- (void)tryBlock:(void(^ _Nonnull)(void))unsafeBlock {
- (void)tryBlock:(__attribute__((noescape)) void(^ _Nonnull)(void))unsafeBlock {
@try {
unsafeBlock();
}
@@ -1,5 +1,10 @@
#import "NMBStringify.h"
#if __has_include("Nimble-Swift.h")
#import "Nimble-Swift.h"
#else
#import <Nimble/Nimble-Swift.h>
#endif
NSString *_Nonnull NMBStringify(id _Nullable anyObject) {
return [NMBStringer stringify:anyObject];
@@ -1,7 +1,12 @@
#import "CurrentTestCaseTracker.h"
#import <XCTest/XCTest.h>
#import <objc/runtime.h>
#if __has_include("Nimble-Swift.h")
#import "Nimble-Swift.h"
#else
#import <Nimble/Nimble-Swift.h>
#endif
#pragma mark - Method Swizzling
/// Swaps the implementations between two instance methods.
+1025 -943
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1010"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1AB61EB02FDF0033DCB1F8416419F110"
BuildableName = "SwiftAudio.framework"
BlueprintName = "SwiftAudio"
ReferencedContainer = "container:Pods.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1AB61EB02FDF0033DCB1F8416419F110"
BuildableName = "SwiftAudio.framework"
BlueprintName = "SwiftAudio"
ReferencedContainer = "container:Pods.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
+9 -4
View File
@@ -85,6 +85,7 @@ extension World {
}
#endif
@nonobjc
internal func it(_ description: String, flags: FilterFlags, file: String, line: UInt, closure: @escaping () -> Void) {
if beforesCurrentlyExecuting {
raiseError("'it' cannot be used inside 'beforeEach', 'it' may only be used inside 'context' or 'describe'. ")
@@ -100,18 +101,21 @@ extension World {
currentExampleGroup.appendExample(example)
}
@nonobjc
internal func fit(_ description: String, flags: FilterFlags, file: String, line: UInt, closure: @escaping () -> Void) {
var focusedFlags = flags
focusedFlags[Filter.focused] = true
self.it(description, flags: focusedFlags, file: file, line: line, closure: closure)
}
@nonobjc
internal func xit(_ description: String, flags: FilterFlags, file: String, line: UInt, closure: @escaping () -> Void) {
var pendingFlags = flags
pendingFlags[Filter.pending] = true
self.it(description, flags: pendingFlags, file: file, line: line, closure: closure)
}
@nonobjc
internal func itBehavesLike(_ name: String, sharedExampleContext: @escaping SharedExampleContext, flags: FilterFlags, file: String, line: UInt) {
guard currentExampleMetadata == nil else {
raiseError("'itBehavesLike' cannot be used inside '\(currentPhase)', 'itBehavesLike' may only be used inside 'context' or 'describe'. ")
@@ -131,6 +135,7 @@ extension World {
}
}
@nonobjc
internal func fitBehavesLike(_ name: String, sharedExampleContext: @escaping SharedExampleContext, flags: FilterFlags, file: String, line: UInt) {
var focusedFlags = flags
focusedFlags[Filter.focused] = true
@@ -169,22 +174,22 @@ extension World {
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
@objc(itWithDescription:flags:file:line:closure:)
private func objc_it(_ description: String, flags: FilterFlags, file: String, line: UInt, closure: @escaping () -> Void) {
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)
}
@objc(fitWithDescription:flags:file:line:closure:)
private func objc_fit(_ description: String, flags: FilterFlags, file: String, line: UInt, closure: @escaping () -> Void) {
internal func objc_fit(_ description: String, flags: FilterFlags, file: String, line: UInt, closure: @escaping () -> Void) {
fit(description, flags: flags, file: file, line: line, closure: closure)
}
@objc(xitWithDescription:flags:file:line:closure:)
private func objc_xit(_ description: String, flags: FilterFlags, file: String, line: UInt, closure: @escaping () -> Void) {
internal func objc_xit(_ description: String, flags: FilterFlags, file: String, line: UInt, closure: @escaping () -> Void) {
xit(description, flags: flags, file: file, line: line, closure: closure)
}
@objc(itBehavesLikeSharedExampleNamed:sharedExampleContext:flags:file:line:)
private func objc_itBehavesLike(_ name: String, sharedExampleContext: @escaping SharedExampleContext, flags: FilterFlags, file: String, line: UInt) {
internal func objc_itBehavesLike(_ name: String, sharedExampleContext: @escaping SharedExampleContext, flags: FilterFlags, file: String, line: UInt) {
itBehavesLike(name, sharedExampleContext: sharedExampleContext, flags: flags, file: file, line: line)
}
#endif
+3
View File
@@ -81,6 +81,9 @@ final public class Example: _ExampleBase {
let exampleMetadata = ExampleMetadata(example: self, exampleIndex: numberOfExamplesRun)
world.currentExampleMetadata = exampleMetadata
defer {
world.currentExampleMetadata = nil
}
world.exampleHooks.executeBefores(exampleMetadata)
group!.phase = .beforesExecuting
@@ -1,8 +1,7 @@
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
import Foundation
public extension NSString {
extension NSString {
private static var invalidCharacters: CharacterSet = {
var invalidCharacters = CharacterSet()
@@ -22,12 +21,29 @@ public extension NSString {
return invalidCharacters
}()
/// This API is not meant to be used outside Quick, so will be unavaialbe in
/// a next major version.
@objc(qck_c99ExtendedIdentifier)
var c99ExtendedIdentifier: String {
public var c99ExtendedIdentifier: String {
let validComponents = components(separatedBy: NSString.invalidCharacters)
let result = validComponents.joined(separator: "_")
return result.isEmpty ? "_" : result
}
}
/// Extension methods or properties for NSObject subclasses are invisible from
/// the Objective-C runtime on static linking unless the consumers add `-ObjC`
/// linker flag, so let's make a wrapper class to mitigate that situation.
///
/// See: https://github.com/Quick/Quick/issues/785 and https://github.com/Quick/Quick/pull/803
@objc
class QCKObjCStringUtils: NSObject {
override private init() {}
@objc
static func c99ExtendedIdentifier(from string: String) -> String {
return string.c99ExtendedIdentifier
}
}
#endif
+1 -1
View File
@@ -160,7 +160,7 @@ final internal class World: _WorldBase {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
@objc(examplesForSpecClass:)
private func objc_examples(_ specClass: AnyClass) -> [Example] {
internal func objc_examples(_ specClass: AnyClass) -> [Example] {
return examples(specClass)
}
#endif
@@ -1,7 +1,12 @@
#import "QuickConfiguration.h"
#import "World.h"
#import <objc/runtime.h>
#if __has_include("Quick-Swift.h")
#import "Quick-Swift.h"
#else
#import <Quick/Quick-Swift.h>
#endif
typedef void (^QCKClassEnumerationBlock)(Class klass);
/**
+6 -2
View File
@@ -1,6 +1,10 @@
#import "QCKDSL.h"
#import "World.h"
#import "World+DSL.h"
#if __has_include("Quick-Swift.h")
#import "Quick-Swift.h"
#else
#import <Quick/Quick-Swift.h>
#endif
void qck_beforeSuite(QCKDSLEmptyBlock closure) {
[[World sharedWorld] beforeSuite:closure];
@@ -1,20 +0,0 @@
#import <Quick/Quick-Swift.h>
@interface World (SWIFT_EXTENSION(Quick))
- (void)beforeSuite:(void (^ __nonnull)(void))closure;
- (void)afterSuite:(void (^ __nonnull)(void))closure;
- (void)sharedExamples:(NSString * __nonnull)name closure:(void (^ __nonnull)(NSDictionary * __nonnull (^ __nonnull)(void)))closure;
- (void)describe:(NSString * __nonnull)description flags:(NSDictionary * __nonnull)flags closure:(void (^ __nonnull)(void))closure;
- (void)context:(NSString * __nonnull)description flags:(NSDictionary * __nonnull)flags closure:(void (^ __nonnull)(void))closure;
- (void)fdescribe:(NSString * __nonnull)description flags:(NSDictionary * __nonnull)flags closure:(void (^ __nonnull)(void))closure;
- (void)xdescribe:(NSString * __nonnull)description flags:(NSDictionary * __nonnull)flags closure:(void (^ __nonnull)(void))closure;
- (void)beforeEach:(void (^ __nonnull)(void))closure;
- (void)beforeEachWithMetadata:(void (^ __nonnull)(ExampleMetadata * __nonnull))closure;
- (void)afterEach:(void (^ __nonnull)(void))closure;
- (void)afterEachWithMetadata:(void (^ __nonnull)(ExampleMetadata * __nonnull))closure;
- (void)itWithDescription:(NSString * __nonnull)description flags:(NSDictionary * __nonnull)flags file:(NSString * __nonnull)file line:(NSUInteger)line closure:(void (^ __nonnull)(void))closure;
- (void)fitWithDescription:(NSString * __nonnull)description flags:(NSDictionary * __nonnull)flags file:(NSString * __nonnull)file line:(NSUInteger)line closure:(void (^ __nonnull)(void))closure;
- (void)xitWithDescription:(NSString * __nonnull)description flags:(NSDictionary * __nonnull)flags file:(NSString * __nonnull)file line:(NSUInteger)line closure:(void (^ __nonnull)(void))closure;
- (void)itBehavesLikeSharedExampleNamed:(NSString * __nonnull)name sharedExampleContext:(NSDictionary * __nonnull (^ __nonnull)(void))sharedExampleContext flags:(NSDictionary * __nonnull)flags file:(NSString * __nonnull)file line:(NSUInteger)line;
- (void)pending:(NSString * __nonnull)description closure:(void (^ __nonnull)(void))closure;
@end
+6
View File
@@ -47,4 +47,10 @@
*/
- (void)spec;
/**
Returns the currently executing spec. Use in specs that require XCTestCase
methds, e.g. expectationWithDescription.
*/
@property (class, nonatomic, readonly) QuickSpec *current;
@end
+11 -3
View File
@@ -1,7 +1,11 @@
#import "QuickSpec.h"
#import "QuickConfiguration.h"
#import "World.h"
#if __has_include("Quick-Swift.h")
#import "Quick-Swift.h"
#else
#import <Quick/Quick-Swift.h>
#endif
static QuickSpec *currentSpec = nil;
@@ -75,6 +79,10 @@ static QuickSpec *currentSpec = nil;
- (void)spec { }
+ (QuickSpec*) current {
return currentSpec;
}
#pragma mark - Internal Methods
/**
@@ -101,8 +109,8 @@ static QuickSpec *currentSpec = nil;
});
const char *types = [[NSString stringWithFormat:@"%s%s%s", @encode(void), @encode(id), @encode(SEL)] UTF8String];
NSString *originalName = example.name.qck_c99ExtendedIdentifier;
NSString *originalName = [QCKObjCStringUtils c99ExtendedIdentifierFrom:example.name];
NSString *selectorName = originalName;
NSUInteger i = 2;
-18
View File
@@ -1,18 +0,0 @@
#import <Quick/Quick-Swift.h>
@class ExampleGroup;
@class ExampleMetadata;
SWIFT_CLASS("_TtC5Quick5World")
@interface World
@property (nonatomic) ExampleGroup * __nullable currentExampleGroup;
@property (nonatomic) ExampleMetadata * __nullable currentExampleMetadata;
@property (nonatomic) BOOL isRunningAdditionalSuites;
+ (World * __nonnull)sharedWorld;
- (void)configure:(void (^ __nonnull)(Configuration * __nonnull))closure;
- (void)finalizeConfiguration;
- (ExampleGroup * __nonnull)rootExampleGroupForSpecClass:(Class __nonnull)cls;
- (NSArray * __nonnull)examplesForSpecClass:(Class __nonnull)specClass;
- (void)performWithCurrentExampleGroup:(ExampleGroup * __nonnull)group closure:(void (^ __nonnull)(void))closure;
@end
@@ -1,6 +1,11 @@
#import <XCTest/XCTest.h>
#import <objc/runtime.h>
#if __has_include("Quick-Swift.h")
#import "Quick-Swift.h"
#else
#import <Quick/Quick-Swift.h>
#endif
@interface XCTestSuite (QuickTestSuiteBuilder)
@end
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>7.0.3</string>
<string>7.3.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
+5 -5
View File
@@ -1,12 +1,12 @@
CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/Nimble
APPLICATION_EXTENSION_API_ONLY = YES
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Nimble
ENABLE_BITCODE = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
OTHER_LDFLAGS = -weak-lswiftXCTest -weak_framework "XCTest"
OTHER_LDFLAGS = $(inherited) -Xlinker -no_application_extension -weak-lswiftXCTest -weak_framework "XCTest"
OTHER_SWIFT_FLAGS = $(inherited) -suppress-warnings $(inherited) "-D" "COCOAPODS"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/Nimble
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
@@ -1,15 +1,28 @@
#!/bin/sh
set -e
set -u
set -o pipefail
if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then
# If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy
# frameworks to, so exit 0 (signalling the script phase was successful).
exit 0
fi
echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}"
SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
# Used as a return value for each invocation of `strip_invalid_archs` function.
STRIP_BINARY_RETVAL=0
# This protects against multiple targets copying the same framework dependency at the same time. The solution
# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
# Copies and strips a vendored framework
install_framework()
{
if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
@@ -58,21 +71,40 @@ install_framework()
fi
}
# Copies the dSYM of a vendored framework
# Copies and strips a vendored dSYM
install_dsym() {
local source="$1"
if [ -r "$source" ]; then
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DWARF_DSYM_FOLDER_PATH}\""
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DWARF_DSYM_FOLDER_PATH}"
# Copy the dSYM into a the targets temp dir.
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\""
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}"
local basename
basename="$(basename -s .framework.dSYM "$source")"
binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}"
# Strip invalid architectures so "fat" simulator / device frameworks work on device
if [[ "$(file "$binary")" == *"Mach-O dSYM companion"* ]]; then
strip_invalid_archs "$binary"
fi
if [[ $STRIP_BINARY_RETVAL == 1 ]]; then
# Move the stripped file into its final destination.
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\""
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}"
else
# The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing.
touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM"
fi
fi
}
# Signs a framework with the provided identity
code_sign_if_enabled() {
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
# Use the current code_sign_identitiy
echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'"
local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'"
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
code_sign_cmd="$code_sign_cmd &"
@@ -85,10 +117,18 @@ code_sign_if_enabled() {
# Strip invalid architectures
strip_invalid_archs() {
binary="$1"
# Get architectures for current file
archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)"
# Get architectures for current target binary
binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)"
# Intersect them with the architectures we are building for
intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)"
# If there are no archs supported by this binary then warn the user
if [[ -z "$intersected_archs" ]]; then
echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)."
STRIP_BINARY_RETVAL=0
return
fi
stripped=""
for arch in $archs; do
for arch in $binary_archs; do
if ! [[ "${ARCHS}" == *"$arch"* ]]; then
# Strip non-valid architectures in-place
lipo -remove "$arch" -output "$binary" "$binary" || exit 1
@@ -98,6 +138,7 @@ strip_invalid_archs() {
if [[ "$stripped" ]]; then
echo "Stripped $binary of architectures:$stripped"
fi
STRIP_BINARY_RETVAL=1
}
@@ -1,5 +1,13 @@
#!/bin/sh
set -e
set -u
set -o pipefail
if [ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH+x} ]; then
# If UNLOCALIZED_RESOURCES_FOLDER_PATH is not set, then there's nowhere for us to copy
# resources to, so exit 0 (signalling the script phase was successful).
exit 0
fi
mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
@@ -12,7 +20,7 @@ XCASSET_FILES=()
# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
case "${TARGETED_DEVICE_FAMILY}" in
case "${TARGETED_DEVICE_FAMILY:-}" in
1,2)
TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
;;
@@ -92,7 +100,7 @@ if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
fi
rm -f "$RESOURCES_TO_COPY"
if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ]
if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "${XCASSET_FILES:-}" ]
then
# Find all other xcassets (this unfortunately includes those of path pods and other targets).
OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
@@ -102,5 +110,9 @@ then
fi
done <<<"$OTHER_XCASSETS"
printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then
printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
else
printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist"
fi
fi
@@ -1,11 +1,11 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/SwiftAudio"
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/SwiftAudio"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/SwiftAudio/SwiftAudio.framework/Headers"
OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SwiftAudio/SwiftAudio.framework/Headers"
OTHER_LDFLAGS = $(inherited) -framework "SwiftAudio"
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
PODS_ROOT = ${SRCROOT}/Pods
@@ -1,11 +1,11 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/SwiftAudio"
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/SwiftAudio"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/SwiftAudio/SwiftAudio.framework/Headers"
OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SwiftAudio/SwiftAudio.framework/Headers"
OTHER_LDFLAGS = $(inherited) -framework "SwiftAudio"
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
PODS_ROOT = ${SRCROOT}/Pods
@@ -1,15 +1,28 @@
#!/bin/sh
set -e
set -u
set -o pipefail
if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then
# If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy
# frameworks to, so exit 0 (signalling the script phase was successful).
exit 0
fi
echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}"
SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
# Used as a return value for each invocation of `strip_invalid_archs` function.
STRIP_BINARY_RETVAL=0
# This protects against multiple targets copying the same framework dependency at the same time. The solution
# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
# Copies and strips a vendored framework
install_framework()
{
if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
@@ -58,21 +71,40 @@ install_framework()
fi
}
# Copies the dSYM of a vendored framework
# Copies and strips a vendored dSYM
install_dsym() {
local source="$1"
if [ -r "$source" ]; then
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DWARF_DSYM_FOLDER_PATH}\""
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DWARF_DSYM_FOLDER_PATH}"
# Copy the dSYM into a the targets temp dir.
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\""
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}"
local basename
basename="$(basename -s .framework.dSYM "$source")"
binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}"
# Strip invalid architectures so "fat" simulator / device frameworks work on device
if [[ "$(file "$binary")" == *"Mach-O dSYM companion"* ]]; then
strip_invalid_archs "$binary"
fi
if [[ $STRIP_BINARY_RETVAL == 1 ]]; then
# Move the stripped file into its final destination.
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\""
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}"
else
# The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing.
touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM"
fi
fi
}
# Signs a framework with the provided identity
code_sign_if_enabled() {
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
# Use the current code_sign_identitiy
echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'"
local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'"
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
code_sign_cmd="$code_sign_cmd &"
@@ -85,10 +117,18 @@ code_sign_if_enabled() {
# Strip invalid architectures
strip_invalid_archs() {
binary="$1"
# Get architectures for current file
archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)"
# Get architectures for current target binary
binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)"
# Intersect them with the architectures we are building for
intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)"
# If there are no archs supported by this binary then warn the user
if [[ -z "$intersected_archs" ]]; then
echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)."
STRIP_BINARY_RETVAL=0
return
fi
stripped=""
for arch in $archs; do
for arch in $binary_archs; do
if ! [[ "${ARCHS}" == *"$arch"* ]]; then
# Strip non-valid architectures in-place
lipo -remove "$arch" -output "$binary" "$binary" || exit 1
@@ -98,6 +138,7 @@ strip_invalid_archs() {
if [[ "$stripped" ]]; then
echo "Stripped $binary of architectures:$stripped"
fi
STRIP_BINARY_RETVAL=1
}
@@ -1,5 +1,13 @@
#!/bin/sh
set -e
set -u
set -o pipefail
if [ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH+x} ]; then
# If UNLOCALIZED_RESOURCES_FOLDER_PATH is not set, then there's nowhere for us to copy
# resources to, so exit 0 (signalling the script phase was successful).
exit 0
fi
mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
@@ -12,7 +20,7 @@ XCASSET_FILES=()
# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
case "${TARGETED_DEVICE_FAMILY}" in
case "${TARGETED_DEVICE_FAMILY:-}" in
1,2)
TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
;;
@@ -92,7 +100,7 @@ if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
fi
rm -f "$RESOURCES_TO_COPY"
if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ]
if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "${XCASSET_FILES:-}" ]
then
# Find all other xcassets (this unfortunately includes those of path pods and other targets).
OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
@@ -102,5 +110,9 @@ then
fi
done <<<"$OTHER_XCASSETS"
printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then
printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
else
printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist"
fi
fi
@@ -1,11 +1,11 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
FRAMEWORK_SEARCH_PATHS = $(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks "$PODS_CONFIGURATION_BUILD_DIR/Nimble" "$PODS_CONFIGURATION_BUILD_DIR/Quick" "$PODS_CONFIGURATION_BUILD_DIR/SwiftAudio"
FRAMEWORK_SEARCH_PATHS = $(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks "${PODS_CONFIGURATION_BUILD_DIR}/Nimble" "${PODS_CONFIGURATION_BUILD_DIR}/Quick" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftAudio"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Nimble/Nimble.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/Quick/Quick.framework/Headers" $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/SwiftAudio/SwiftAudio.framework/Headers"
OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Nimble/Nimble.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Quick/Quick.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SwiftAudio/SwiftAudio.framework/Headers"
OTHER_LDFLAGS = $(inherited) -framework "Nimble" -framework "Quick"
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
PODS_ROOT = ${SRCROOT}/Pods
@@ -1,11 +1,11 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
FRAMEWORK_SEARCH_PATHS = $(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks "$PODS_CONFIGURATION_BUILD_DIR/Nimble" "$PODS_CONFIGURATION_BUILD_DIR/Quick" "$PODS_CONFIGURATION_BUILD_DIR/SwiftAudio"
FRAMEWORK_SEARCH_PATHS = $(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks "${PODS_CONFIGURATION_BUILD_DIR}/Nimble" "${PODS_CONFIGURATION_BUILD_DIR}/Quick" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftAudio"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Nimble/Nimble.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/Quick/Quick.framework/Headers" $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/SwiftAudio/SwiftAudio.framework/Headers"
OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Nimble/Nimble.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Quick/Quick.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SwiftAudio/SwiftAudio.framework/Headers"
OTHER_LDFLAGS = $(inherited) -framework "Nimble" -framework "Quick"
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
PODS_ROOT = ${SRCROOT}/Pods
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.2.0</string>
<string>1.3.2</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
+5 -5
View File
@@ -1,12 +1,12 @@
CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/Quick
APPLICATION_EXTENSION_API_ONLY = YES
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Quick
ENABLE_BITCODE = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
OTHER_LDFLAGS = -framework "XCTest"
OTHER_LDFLAGS = $(inherited) -Xlinker -no_application_extension -framework "XCTest"
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/Quick
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<string>0.3.3</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
@@ -1,9 +1,8 @@
CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/SwiftAudio
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftAudio
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/../..
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
+26 -48
View File
@@ -29,8 +29,8 @@
07732654205ECA8B00C4D1CD /* WAV-MP3.wav in Resources */ = {isa = PBXBuildFile; fileRef = 07732650205EACA300C4D1CD /* WAV-MP3.wav */; };
07732655205ECE1C00C4D1CD /* nasa_throttle_up.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 07732652205EB1B500C4D1CD /* nasa_throttle_up.mp3 */; };
0775575920668B020002C6A1 /* QueueManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0775575820668B020002C6A1 /* QueueManagerTests.swift */; };
07756B69218A4E870023935E /* AudioSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07756B68218A4E870023935E /* AudioSession.swift */; };
078C908F210D263200555E80 /* AVPlayerItemObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 078C908D210D25F700555E80 /* AVPlayerItemObserverTests.swift */; };
07CC171C213E912E005F880E /* SimpleAudioPlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07CC171A213E912A005F880E /* SimpleAudioPlayerTests.swift */; };
07DBB1E1212C17E600BB4278 /* QueuedAudioPlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07DBB1E0212C17E600BB4278 /* QueuedAudioPlayerTests.swift */; };
607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; };
607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; };
@@ -68,8 +68,8 @@
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>"; };
07CC171A213E912A005F880E /* SimpleAudioPlayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleAudioPlayerTests.swift; sourceTree = "<group>"; };
07DBB1E0212C17E600BB4278 /* QueuedAudioPlayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueuedAudioPlayerTests.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; };
@@ -84,7 +84,7 @@
607FACEB1AFB9204008FA782 /* AVPlayerObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerObserverTests.swift; sourceTree = "<group>"; };
768DA07BCD292FA8C3F43CF5 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
928EC55949C3B1093DA7BC4C /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
B88D29AD5BC56C1834C04294 /* SwiftAudio.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = SwiftAudio.podspec; path = ../SwiftAudio.podspec; sourceTree = "<group>"; };
B88D29AD5BC56C1834C04294 /* SwiftAudio.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = SwiftAudio.podspec; path = ../SwiftAudio.podspec; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
C344B34C66182CD1C5AD6DD2 /* Pods-SwiftAudio_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftAudio_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftAudio_Example/Pods-SwiftAudio_Example.debug.xcconfig"; sourceTree = "<group>"; };
C4C4423F4BAFCE03507BC386 /* Pods-SwiftAudio_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftAudio_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftAudio_Tests/Pods-SwiftAudio_Tests.release.xcconfig"; sourceTree = "<group>"; };
C8489AD5161CEA9C48D7DF42 /* Pods_SwiftAudio_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftAudio_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -124,6 +124,14 @@
path = Source;
sourceTree = "<group>";
};
07756B67218A4E640023935E /* Mocks */ = {
isa = PBXGroup;
children = (
07756B68218A4E870023935E /* AudioSession.swift */,
);
path = Mocks;
sourceTree = "<group>";
};
607FACC71AFB9204008FA782 = {
isa = PBXGroup;
children = (
@@ -175,6 +183,7 @@
607FACE81AFB9204008FA782 /* Tests */ = {
isa = PBXGroup;
children = (
07756B67218A4E640023935E /* Mocks */,
0708ED732116EE0100EB29BD /* AudioPlayerTests.swift */,
607FACEB1AFB9204008FA782 /* AVPlayerObserverTests.swift */,
074A6482205C155E0083D868 /* AVPlayerTimeObserverTests.swift */,
@@ -184,7 +193,6 @@
078C908D210D25F700555E80 /* AVPlayerItemObserverTests.swift */,
0708ED6B2116DA4B00EB29BD /* AudioSessionControllerTests.swift */,
07DBB1E0212C17E600BB4278 /* QueuedAudioPlayerTests.swift */,
07CC171A213E912A005F880E /* SimpleAudioPlayerTests.swift */,
0708ED712116E91300EB29BD /* Source */,
607FACE91AFB9204008FA782 /* Supporting Files */,
);
@@ -241,7 +249,6 @@
607FACCD1AFB9204008FA782 /* Frameworks */,
607FACCE1AFB9204008FA782 /* Resources */,
92F611A7E298DE7BCA0B0B61 /* [CP] Embed Pods Frameworks */,
D90D8AB1C4A6EFC8D653227D /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -261,7 +268,6 @@
607FACE21AFB9204008FA782 /* Frameworks */,
607FACE31AFB9204008FA782 /* Resources */,
D41B1A785DE273F72BD47633 /* [CP] Embed Pods Frameworks */,
EF21C94415B1482F4674E093 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -280,13 +286,13 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0830;
LastUpgradeCheck = 0830;
LastUpgradeCheck = 1010;
ORGANIZATIONNAME = CocoaPods;
TargetAttributes = {
607FACCF1AFB9204008FA782 = {
CreatedOnToolsVersion = 6.3.1;
DevelopmentTeam = HPNZWPB9JK;
LastSwiftMigration = 0900;
LastSwiftMigration = 1010;
SystemCapabilities = {
com.apple.BackgroundModes = {
enabled = 1;
@@ -296,7 +302,7 @@
607FACE41AFB9204008FA782 = {
CreatedOnToolsVersion = 6.3.1;
DevelopmentTeam = HPNZWPB9JK;
LastSwiftMigration = 0900;
LastSwiftMigration = 1010;
TestTargetID = 607FACCF1AFB9204008FA782;
};
};
@@ -406,36 +412,6 @@
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SwiftAudio_Tests/Pods-SwiftAudio_Tests-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
D90D8AB1C4A6EFC8D653227D /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SwiftAudio_Example/Pods-SwiftAudio_Example-resources.sh\"\n";
showEnvVarsInLog = 0;
};
EF21C94415B1482F4674E093 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SwiftAudio_Tests/Pods-SwiftAudio_Tests-resources.sh\"\n";
showEnvVarsInLog = 0;
};
F22DFA58013500305552C406 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -475,9 +451,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
07756B69218A4E870023935E /* AudioSession.swift in Sources */,
0708ED702116E89900EB29BD /* Source.swift in Sources */,
0708ED742116EE0100EB29BD /* AudioPlayerTests.swift in Sources */,
07CC171C213E912E005F880E /* SimpleAudioPlayerTests.swift in Sources */,
0775575920668B020002C6A1 /* QueueManagerTests.swift in Sources */,
074A6483205C155E0083D868 /* AVPlayerTimeObserverTests.swift in Sources */,
078C908F210D263200555E80 /* AVPlayerItemObserverTests.swift in Sources */,
@@ -531,12 +507,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;
@@ -584,12 +562,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;
@@ -630,8 +610,8 @@
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 = 4.2;
TARGETED_DEVICE_FAMILY = 1;
};
name = Debug;
};
@@ -647,8 +627,8 @@
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 = 4.2;
TARGETED_DEVICE_FAMILY = 1;
};
name = Release;
};
@@ -669,8 +649,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 = 4.2;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftAudio_Example.app/SwiftAudio_Example";
};
name = Debug;
@@ -688,8 +667,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 = 4.2;
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,5 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
<dict>
<key>BuildSystemType</key>
<string>Original</string>
</dict>
</plist>
+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()
+5 -1
View File
@@ -13,7 +13,7 @@ import SwiftAudio
class AudioController {
static let shared = AudioController()
let player = QueuedAudioPlayer()
let player: QueuedAudioPlayer
let audioSessionController = AudioSessionController.shared
let sources: [AudioItem] = [
@@ -24,8 +24,12 @@ class AudioController {
]
init() {
let controller = RemoteCommandController()
player = QueuedAudioPlayer(remoteCommandController: controller)
player.remoteCommands = [
.stop,
.play,
.pause,
.togglePlayPause,
.next,
.previous,
+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
}
+6 -6
View File
@@ -34,7 +34,7 @@ class ViewController: UIViewController {
if (!controller.audioSessionController.audioSessionIsActive) {
try? controller.audioSessionController.activateSession()
}
try? controller.player.togglePlaying()
controller.player.togglePlaying()
}
@IBAction func previous(_ sender: Any) {
@@ -50,7 +50,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) {
@@ -61,6 +61,10 @@ class ViewController: UIViewController {
}
extension ViewController: AudioPlayerDelegate {
func audioPlayer(itemPlaybackEndedWithReason reason: PlaybackEndedReason) {
}
func audioPlayer(playerDidChangeState state: AVPlayerWrapperState) {
playButton.setTitle(state == .playing ? "Pause" : "Play", for: .normal)
@@ -89,10 +93,6 @@ extension ViewController: AudioPlayerDelegate {
}
}
func audioPlayerItemDidComplete() {
}
func audioPlayer(secondsElapsed seconds: Double) {
if !isScrubbing {
slider.setValue(Float(seconds), animated: false)
+4 -4
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() {
@@ -58,11 +58,11 @@ class AVPlayerObserverTests: QuickSpec, AVPlayerObserverDelegate {
}
}
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
}
+89 -31
View File
@@ -1,5 +1,6 @@
import Quick
import Nimble
import AVFoundation
@testable import SwiftAudio
@@ -13,10 +14,11 @@ class AVPlayerWrapperTests: QuickSpec {
var wrapper: AVPlayerWrapper!
beforeEach {
wrapper = AVPlayerWrapper()
wrapper.automaticallyWaitsToMinimizeStalling = false
let player = AVPlayer()
player.automaticallyWaitsToMinimizeStalling = false
player.volume = 0.0
wrapper = AVPlayerWrapper(avPlayer: player)
wrapper.bufferDuration = 0.0001
wrapper.volume = 0.0
}
describe("its state", {
@@ -26,12 +28,8 @@ class AVPlayerWrapperTests: QuickSpec {
context("when loading a source", {
beforeEach {
try? wrapper.load(fromFilePath: Source.path, playWhenReady: false)
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))
@@ -40,7 +38,7 @@ class AVPlayerWrapperTests: QuickSpec {
context("when playing with no source", {
beforeEach {
try? wrapper.play()
wrapper.play()
}
it("should be idle", closure: {
expect(wrapper.state).to(equal(AVPlayerWrapperState.idle))
@@ -49,7 +47,7 @@ class AVPlayerWrapperTests: QuickSpec {
context("when playing a source", {
beforeEach {
try? wrapper.load(fromFilePath: Source.path, playWhenReady: true)
wrapper.load(from: URL(fileURLWithPath: Source.path), playWhenReady: true)
}
it("should eventually be playing", closure: {
@@ -66,10 +64,10 @@ class AVPlayerWrapperTests: QuickSpec {
wrapper.delegate = holder
holder.stateUpdate = { (state) in
if state == .playing {
try? wrapper.pause()
wrapper.pause()
}
}
try? wrapper.load(fromFilePath: Source.path, playWhenReady: true)
wrapper.load(from: URL(fileURLWithPath: Source.path), playWhenReady: true)
}
it("should eventually be paused", closure: {
@@ -83,10 +81,10 @@ class AVPlayerWrapperTests: QuickSpec {
wrapper.delegate = holder
holder.stateUpdate = { (state) in
if state == .playing {
try? wrapper.togglePlaying()
wrapper.togglePlaying()
}
}
try? wrapper.load(fromFilePath: Source.path, playWhenReady: true)
wrapper.load(from: URL(fileURLWithPath: Source.path), playWhenReady: true)
}
it("should eventually be paused", closure: {
expect(wrapper.state).toEventually(equal(AVPlayerWrapperState.paused))
@@ -109,7 +107,7 @@ class AVPlayerWrapperTests: QuickSpec {
receivedIdleUpdate = true
}
}
try? wrapper.load(fromFilePath: Source.path, playWhenReady: true)
wrapper.load(from: URL(fileURLWithPath: Source.path), playWhenReady: true)
}
it("should eventually be 'idle'", closure: {
@@ -120,12 +118,23 @@ class AVPlayerWrapperTests: QuickSpec {
context("when seeking before loading", {
beforeEach {
try? wrapper.seek(to: 10)
wrapper.seek(to: 10)
}
it("should be idle", closure: {
expect(wrapper.state).to(equal(AVPlayerWrapperState.idle))
})
})
context("when loading source with initial time", closure: {
let initialTime: TimeInterval = 4.0
beforeEach {
wrapper.load(from: LongSource.url, playWhenReady: true, initialTime: initialTime)
}
it("should eventually be playing", closure: {
expect(wrapper.state).toEventually(equal(AVPlayerWrapperState.playing))
})
})
})
describe("its duration", {
@@ -135,7 +144,7 @@ class AVPlayerWrapperTests: QuickSpec {
context("when loading source", {
beforeEach {
try? wrapper.load(fromFilePath: Source.path, playWhenReady: false)
wrapper.load(from: URL(fileURLWithPath: LongSource.path), playWhenReady: false)
}
it("should eventually not be 0", closure: {
expect(wrapper.duration).toEventuallyNot(equal(0))
@@ -153,18 +162,70 @@ class AVPlayerWrapperTests: QuickSpec {
let seekTime: TimeInterval = 0.5
beforeEach {
wrapper.delegate = holder
holder.stateUpdate = { (state) in
if state == .ready && wrapper.duration != 0 {
try? wrapper.seek(to: seekTime)
}
}
try? wrapper.load(fromFilePath: Source.path, playWhenReady: false)
wrapper.load(from: Source.url, playWhenReady: false)
wrapper.seek(to: seekTime)
}
it("should eventually be equal to the seeked time", closure: {
expect(wrapper.currentTime).toEventually(equal(seekTime))
})
})
context("when playing from initial time", closure: {
let initialTime: TimeInterval = 4.0
beforeEach {
wrapper.load(from: LongSource.url, playWhenReady: false, initialTime: initialTime)
}
it("should eventuallt be equal to the initial time", closure: {
expect(wrapper.currentTime).toEventually(equal(initialTime))
})
})
})
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()))
})
})
})
}
@@ -174,12 +235,12 @@ class AVPlayerWrapperTests: QuickSpec {
}
class AVPlayerWrapperDelegateHolder: AVPlayerWrapperDelegate {
func AVWrapper(itemPlaybackDoneWithReason reason: PlaybackEndedReason) {
}
var state: AVPlayerWrapperState? {
didSet {
print(state)
if let state = state {
self.stateUpdate?(state)
}
@@ -193,10 +254,6 @@ class AVPlayerWrapperDelegateHolder: AVPlayerWrapperDelegate {
self.state = state
}
func AVWrapperItemDidComplete() {
}
func AVWrapper(secondsElapsed seconds: Double) {
}
@@ -205,8 +262,9 @@ class AVPlayerWrapperDelegateHolder: AVPlayerWrapperDelegate {
}
var seekCompletion: (() -> Void)?
func AVWrapper(seekTo seconds: Int, didFinish: Bool) {
seekCompletion?()
}
func AVWrapper(didUpdateDuration duration: Double) {
+76 -24
View File
@@ -1,5 +1,6 @@
import Quick
import Nimble
import AVFoundation
@testable import SwiftAudio
@@ -11,9 +12,9 @@ class AudioPlayerTests: QuickSpec {
beforeEach {
audioPlayer = AudioPlayer()
audioPlayer.automaticallyWaitsToMinimizeStalling = false
audioPlayer.bufferDuration = 0.0001
audioPlayer.volume = 0
audioPlayer.automaticallyWaitsToMinimizeStalling = false
audioPlayer.volume = 0.0
}
describe("its state", {
@@ -24,7 +25,7 @@ class AudioPlayerTests: QuickSpec {
context("when audio item is loaded", {
beforeEach {
try? audioPlayer.loadItem(Source.getAudioItem(), playWhenReady: false)
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
}
it("it should eventually be ready", closure: {
@@ -34,7 +35,7 @@ class AudioPlayerTests: QuickSpec {
context("when an item is loaded (playWhenReady=true)", {
beforeEach {
try? audioPlayer.loadItem(Source.getAudioItem(), playWhenReady: true)
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
}
it("it should eventually be playing", closure: {
@@ -50,10 +51,10 @@ class AudioPlayerTests: QuickSpec {
holder.stateUpdate = { state in
print(state.rawValue)
if state == .ready {
try? audioPlayer.play()
audioPlayer.play()
}
}
try? audioPlayer.loadItem(Source.getAudioItem(), playWhenReady: false)
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
}
it("should eventually be playing", closure: {
@@ -68,10 +69,10 @@ class AudioPlayerTests: QuickSpec {
audioPlayer.delegate = holder
holder.stateUpdate = { (state) in
if state == .playing {
try? audioPlayer.pause()
audioPlayer.pause()
}
}
try? audioPlayer.loadItem(Source.getAudioItem(), playWhenReady: true)
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
}
it("should eventually be paused", closure: {
@@ -89,7 +90,7 @@ class AudioPlayerTests: QuickSpec {
audioPlayer.stop()
}
}
try? audioPlayer.loadItem(Source.getAudioItem(), playWhenReady: true)
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
}
it("should eventually be idle", closure: {
@@ -105,22 +106,28 @@ class AudioPlayerTests: QuickSpec {
})
context("when seeking to a time", {
let holder = AudioPlayerDelegateHolder()
let seekTime: TimeInterval = 0.5
let seekTime: TimeInterval = 1.0
beforeEach {
audioPlayer.delegate = holder
holder.stateUpdate = { (state) in
if state == .ready && audioPlayer.duration != 0 {
try? audioPlayer.seek(to: seekTime)
}
}
try? audioPlayer.loadItem(Source.getAudioItem(), playWhenReady: false)
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
audioPlayer.seek(to: seekTime)
}
it("should eventually be equal to the seeked time", closure: {
expect(audioPlayer.currentTime).toEventually(equal(seekTime))
})
})
context("when playing an item with an initial time", {
var item: DefaultAudioItemInitialTime!
beforeEach {
item = DefaultAudioItemInitialTime(audioUrl: LongSource.path, artist: nil, title: nil, albumTitle: nil, sourceType: .file, artwork: nil, initialTime: 4.0)
try? audioPlayer.load(item: item, playWhenReady: false)
}
it("should eventaully be equal to the initial time", closure: {
expect(audioPlayer.currentTime).toEventually(equal(item.getInitialTime()))
})
})
})
describe("its rate", {
@@ -130,7 +137,7 @@ class AudioPlayerTests: QuickSpec {
context("when playing an item", {
beforeEach {
try? audioPlayer.loadItem(Source.getAudioItem(), playWhenReady: true)
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
}
it("should eventually be 1.0", closure: {
@@ -138,12 +145,60 @@ class AudioPlayerTests: QuickSpec {
})
})
})
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())
})
})
context("when setting the timePitchAlgorithm", {
beforeEach {
audioPlayer.audioTimePitchAlgorithm = .timeDomain
}
context("then loading an item", {
beforeEach {
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
}
it("should have the applied timePitchAlgorithm", closure: {
expect(audioPlayer.wrapper.currentItem?.audioTimePitchAlgorithm).to(equal(AVAudioTimePitchAlgorithm.timeDomain))
})
})
context("then loading a timepitching item", {
beforeEach {
let item = DefaultAudioItemTimePitching(audioUrl: Source.path, artist: nil, title: nil, albumTitle: nil, sourceType: .file, artwork: nil, audioTimePitchAlgorithm: AVAudioTimePitchAlgorithm.spectral)
try? audioPlayer.load(item: item, playWhenReady: false)
}
it("should have the applied timePitchAlgorithm", closure: {
expect(audioPlayer.wrapper.currentItem?.audioTimePitchAlgorithm).to(equal(AVAudioTimePitchAlgorithm.spectral))
})
})
})
})
}
}
}
class AudioPlayerDelegateHolder: AudioPlayerDelegate {
func audioPlayer(itemPlaybackEndedWithReason reason: PlaybackEndedReason) {
}
var stateUpdate: ((_ state: AudioPlayerState) -> Void)?
var state: AudioPlayerState? {
@@ -158,10 +213,6 @@ class AudioPlayerDelegateHolder: AudioPlayerDelegate {
self.state = state
}
func audioPlayerItemDidComplete() {
}
func audioPlayer(secondsElapsed seconds: Double) {
}
@@ -170,8 +221,9 @@ class AudioPlayerDelegateHolder: AudioPlayerDelegate {
}
var seekCompletion: (() -> Void)?
func audioPlayer(seekTo seconds: Int, didFinish: Bool) {
seekCompletion?()
}
func audioPlayer(didUpdateDuration duration: Double) {
@@ -9,7 +9,7 @@ class AudioSessionControllerTests: QuickSpec {
override func spec() {
describe("An AudioSessionController") {
let audioSessionController: AudioSessionController = AudioSessionController.shared
let audioSessionController: AudioSessionController = AudioSessionController(audioSession: NonFailingAudioSession())
it("should be inactive", closure: {
expect(audioSessionController.audioSessionIsActive).to(beFalse())
@@ -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()
@@ -69,18 +69,32 @@ class AudioSessionControllerTests: QuickSpec {
})
})
}
describe("An AudioSessionController with a failing AudioSession") {
var audioSessionController: AudioSessionController!
beforeEach {
audioSessionController = AudioSessionController(audioSession: FailingAudioSession())
}
context("when activated", {
beforeEach {
try? audioSessionController.activateSession()
}
it("should be inactive", closure: {
expect(audioSessionController.audioSessionIsActive).to(beFalse())
})
})
}
}
}
class AudioSessionControllerDelegateImplementation: AudioSessionControllerDelegate {
var interruptionType: AVAudioSessionInterruptionType? = nil
var interruptionType: AVAudioSession.InterruptionType? = nil
func handleInterruption(type: AVAudioSessionInterruptionType) {
func handleInterruption(type: AVAudioSession.InterruptionType) {
self.interruptionType = type
}
}
+66
View File
@@ -0,0 +1,66 @@
//
// AudioSession.swift
// SwiftAudio_Tests
//
// Created by Jørgen Henrichsen on 31/10/2018.
// Copyright © 2018 CocoaPods. All rights reserved.
//
import Foundation
import AVFoundation
@testable import SwiftAudio
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
func setCategory(_ category: AVAudioSession.Category, mode: AVAudioSession.Mode, options: AVAudioSession.CategoryOptions) 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, 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
func setCategory(_ category: AVAudioSession.Category, mode: AVAudioSession.Mode, options: AVAudioSession.CategoryOptions) throws {
throw AVError(AVError.unknown)
}
func setCategory(_ category: AVAudioSession.Category, mode: AVAudioSession.Mode, policy: AVAudioSession.RouteSharingPolicy, options: AVAudioSession.CategoryOptions) throws {
throw AVError(AVError.unknown)
}
func setActive(_ active: Bool) throws {
throw AVError(AVError.unknown)
}
func setActive(_ active: Bool, options: AVAudioSession.SetActiveOptions) throws {
throw AVError(AVError.unknown)
}
}
+147 -8
View File
@@ -20,6 +20,57 @@ class QueueManagerTests: QuickSpec {
manager = QueueManager()
}
describe("its current item", {
it("should be nil", closure: {
expect(manager.current).to(beNil())
})
context("when one item is added", closure: {
beforeEach {
manager.addItem(self.dummyItem)
}
it("should not be nil", closure: {
expect(manager.current).toNot(beNil())
})
it("should be the added item", closure: {
expect(manager.current).to(equal(self.dummyItem))
})
context("then replaced", closure: {
beforeEach {
manager.replaceCurrentItem(with: 1)
}
it("should be the new item", closure: {
expect(manager.current).to(equal(1))
})
})
})
context("when replaced", closure: {
beforeEach {
manager.replaceCurrentItem(with: 1)
}
it("should not be nil", closure: {
expect(manager.current).toNot(beNil())
})
})
context("when mulitple items are added", {
beforeEach {
manager.addItems(self.dummyItems)
}
it("should not be nil", closure: {
expect(manager.current).toNot(beNil())
})
})
})
context("when adding one item", {
beforeEach {
@@ -30,9 +81,13 @@ class QueueManagerTests: QuickSpec {
expect(manager.items).notTo(beEmpty())
})
it("should set it as the current item", closure: {
expect(manager.current).toNot(beNil())
expect(manager.current).to(equal(self.dummyItem))
context("then replacing the item", closure: {
beforeEach {
manager.replaceCurrentItem(with: 1)
}
it("should have replaced the current item", closure: {
expect(manager.current).to(equal(1))
})
})
context("then calling next", {
@@ -115,14 +170,72 @@ class QueueManagerTests: QuickSpec {
expect(manager.current).to(equal(self.dummyItems.first))
})
})
context("then removing previous items", {
beforeEach {
manager.removePreviousItems()
}
it("should have no previous items", closure: {
expect(manager.previousItems.count).to(equal(0))
})
it("should have current index zero", closure: {
expect(manager.currentIndex).to(equal(0))
})
})
})
context("adding more items", {
var initialItemCount: Int!
let newItems: [Int] = [10, 11, 12, 13]
beforeEach {
initialItemCount = manager.items.count
try? manager.addItems(newItems, at: manager.items.endIndex - 1)
}
it("should have more items", closure: {
expect(manager.items.count).to(equal(initialItemCount + newItems.count))
})
})
context("adding more items at a smaller index than currentIndex", {
var initialCurrentIndex: Int!
let newItems: [Int] = [10, 11, 12, 13]
beforeEach {
initialCurrentIndex = manager.currentIndex
try? manager.addItems(newItems, at: initialCurrentIndex)
}
it("currentIndex should increase by number of new items", closure: {
expect(manager.currentIndex).to(equal(initialCurrentIndex + newItems.count))
})
})
// MARK: - Removal
context("then removing a item with index less than currentIndex", {
beforeEach {
var removed: Int?
var initialCurrentIndex: Int!
beforeEach {
let _ = try? manager.jump(to: 3)
initialCurrentIndex = manager.currentIndex
removed = try? manager.removeItem(at: initialCurrentIndex - 1)
}
it("should remove an item", closure: {
expect(removed).toNot(beNil())
})
it("should decrement the currentIndex", closure: {
expect(manager.currentIndex).to(equal(initialCurrentIndex - 1))
})
}
})
context("then removing the second item", {
var removed: Int?
beforeEach {
removed = try? manager.remove(atIndex: 1)
removed = try? manager.removeItem(at: 1)
}
it("should have one less item", closure: {
@@ -134,7 +247,7 @@ class QueueManagerTests: QuickSpec {
context("then removing the last item", {
var removed: Int?
beforeEach {
removed = try? manager.remove(atIndex: self.dummyItems.count - 1)
removed = try? manager.removeItem(at: self.dummyItems.count - 1)
}
it("should have one less item", closure: {
@@ -146,7 +259,7 @@ class QueueManagerTests: QuickSpec {
context("then removing the current item", {
var removed: Int?
beforeEach {
removed = try? manager.remove(atIndex: manager.currentIndex)
removed = try? manager.removeItem(at: manager.currentIndex)
}
it("should not remove any items", closure: {
expect(removed).to(beNil())
@@ -157,7 +270,7 @@ class QueueManagerTests: QuickSpec {
context("then removing with too large index", {
var removed: Int?
beforeEach {
removed = try? manager.remove(atIndex: self.dummyItems.count)
removed = try? manager.removeItem(at: self.dummyItems.count)
}
it("should not remove any items", closure: {
@@ -169,7 +282,7 @@ class QueueManagerTests: QuickSpec {
context("then removing with too small index", {
var removed: Int?
beforeEach {
removed = try? manager.remove(atIndex: -1)
removed = try? manager.removeItem(at: -1)
}
it("should not remove any items", closure: {
@@ -178,6 +291,16 @@ class QueueManagerTests: QuickSpec {
})
})
context("then removing upcoming items", {
beforeEach {
manager.removeUpcomingItems()
}
it("should have no next items", closure: {
expect(manager.nextItems.count).to(equal(0))
})
})
// MARK: - Jumping
context("then jumping to the current item", {
@@ -343,6 +466,22 @@ class QueueManagerTests: QuickSpec {
expect(manager.items).to(equal(afterMoving))
})
})
// MARK: - Clear
context("when queue is cleared", {
beforeEach {
manager.clearQueue()
}
it("should have currentIndex 0", closure: {
expect(manager.currentIndex).to(equal(0))
})
it("should have no items", closure: {
expect(manager.items.count).to(equal(0))
})
})
})
}
}
+56 -4
View File
@@ -9,9 +9,9 @@ class QueuedAudioPlayerTests: QuickSpec {
var audioPlayer: QueuedAudioPlayer!
beforeEach {
audioPlayer = QueuedAudioPlayer()
audioPlayer.automaticallyWaitsToMinimizeStalling = false
audioPlayer.bufferDuration = 0.0001
audioPlayer.volume = 0
audioPlayer.automaticallyWaitsToMinimizeStalling = false
audioPlayer.volume = 0.0
}
describe("its current item", {
it("should be nil", closure: {
@@ -19,12 +19,24 @@ class QueuedAudioPlayerTests: QuickSpec {
})
context("when adding one item", {
var item: AudioItem!
beforeEach {
try? audioPlayer.add(item: ShortSource.getAudioItem(), playWhenReady: false)
item = ShortSource.getAudioItem()
try? audioPlayer.add(item: item, playWhenReady: false)
}
it("should not be nil", closure: {
expect(audioPlayer.currentItem).toNot(beNil())
})
context("then loading a new item", closure: {
beforeEach {
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
}
it("should have replaced the item", closure: {
expect(audioPlayer.currentItem?.getSourceUrl()).toNot(equal(item.getSourceUrl()))
})
})
})
context("when adding multiple items", {
@@ -70,7 +82,7 @@ class QueuedAudioPlayerTests: QuickSpec {
context("then removing one item", {
beforeEach {
try? audioPlayer.removeItem(atIndex: 1)
try? audioPlayer.removeItem(at: 1)
}
it("should be empty", closure: {
@@ -86,6 +98,26 @@ class QueuedAudioPlayerTests: QuickSpec {
expect(audioPlayer.nextItems.count).to(equal(0))
})
})
context("then removing upcoming items", {
beforeEach {
audioPlayer.removeUpcomingItems()
}
it("should be empty", closure: {
expect(audioPlayer.nextItems.count).to(equal(0))
})
})
context("then stopping", {
beforeEach {
audioPlayer.stop()
}
it("should be empty", closure: {
expect(audioPlayer.nextItems.count).to(equal(0))
})
})
})
})
@@ -112,6 +144,26 @@ class QueuedAudioPlayerTests: QuickSpec {
})
})
context("then removing all previous items", {
beforeEach {
audioPlayer.removePreviousItems()
}
it("should be empty", closure: {
expect(audioPlayer.previousItems.count).to(equal(0))
})
})
context("then stopping", {
beforeEach {
audioPlayer.stop()
}
it("should be empty", closure: {
expect(audioPlayer.previousItems.count).to(equal(0))
})
})
})
})
}
@@ -1,43 +0,0 @@
import Quick
import Nimble
@testable import SwiftAudio
class SimpleAudioPlayerTests: QuickSpec {
override func spec() {
describe("A SimpleAudioPlayer") {
var player: SimpleAudioPlayer!
beforeEach {
player = SimpleAudioPlayer()
player.automaticallyWaitsToMinimizeStalling = false
player.bufferDuration = 0.0001
player.volume = 0
}
describe("its state", {
it("should be idle", closure: {
expect(player.playerState).to(equal(AudioPlayerState.idle))
})
context("when loading an item with playeWhenReady: false", {
beforeEach {
try? player.load(item: Source.getAudioItem(), playWhenReady: false)
}
it("should eventually be ready", closure: {
expect(player.playerState).toEventually(equal(AudioPlayerState.ready))
})
})
context("when loading an item with playWhenReady: true", {
beforeEach {
try? player.load(item: Source.getAudioItem(), playWhenReady: true)
}
it("should eventually be playing", closure: {
expect(player.playerState).toEventually(equal(AudioPlayerState.playing))
})
})
})
}
}
}
+11
View File
@@ -11,6 +11,7 @@ import SwiftAudio
struct Source {
static let path: String = Bundle.main.path(forResource: "TestSound", ofType: "m4a")!
static let url: URL = URL(fileURLWithPath: Source.path)
static func getAudioItem() -> AudioItem {
return DefaultAudioItem(audioUrl: Source.path, sourceType: .file)
@@ -19,8 +20,18 @@ struct Source {
struct ShortSource {
static let path: String = Bundle.main.path(forResource: "ShortTestSound", ofType: "m4a")!
static let url: URL = URL(fileURLWithPath: ShortSource.path)
static func getAudioItem() -> AudioItem {
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)
}
}
+61 -45
View File
@@ -10,7 +10,7 @@ SwiftAudio is an audio player written in Swift, making it simpler to work with a
## Example
To see the audio player in action clone the repo and run the example project!
To see the audio player in action, run the example project!
To run the example project, clone the repo, and run `pod install` from the Example directory first.
## Requirements
@@ -18,54 +18,69 @@ iOS 10.0+
## Installation
### CocoaPods
SwiftAudio is available through [CocoaPods](http://cocoapods.org). To install
it, simply add the following line to your Podfile:
```ruby
pod 'SwiftAudio'
pod 'SwiftAudio', '~> 0.6.2'
```
### Carthage
SwiftAudio supports [Carthage](https://github.com/Carthage/Carthage). Add this to your Cartfile:
```ruby
github "jorgenhenrichsen/SwiftAudio" ~> 0.6.2
```
Then follow the rest of Carthage instructions on [adding a framework](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application).
## Usage
### AudioPlayer
To get started playing some audio:
```swift
let player = AudioPlayer()
let audioItem = DefaultAudioItem(audioUrl: "someUrl", sourceType: .stream)
player.load(item: audioItem, playWhenReady: true) // Load the item and start playing when the player is ready.
```
Implement `AudioPlayerDelegate` to get notified about useful events and updates to the state of the `AudioPlayer`.
#### QueuedAudioPlayer
The `QueuedAudioPlayer` is asubclass of `AudioPlayer` that maintains a queue of audio tracks.
```swift
let player = QueuedAudioPlayer()
let audioItem = DefaultAudioItem(audioUrl: "someUrl", sourceType: .stream)
player.add(item: audioItem)
player.add(item: audioItem, playWhenReady: true) // Since this is the first item, we can supply playWhenReady: true to immedietaly start playing when the item is loaded.
```
The player will load the track and start playing when ready. To disable this behaviour use `add(item:playWhenReady:)` and pass in `false`. This is `true` by default. To get notified of events during playback and loading, implement `AudioPlayerDelegate` and the player will notify you with changes.
When a track is done playing, the player will load the next track and update the queue, as long as `automaticallyPlayNextSong` is `true` (default).
If you want a simpler audio player without queue functionality, use:
##### Navigating the queue
All `AudioItem`s are stored in either `previousItems` or `nextItems`, which refers to items that come prior to the `currentItem` and after, respectively. The queue is navigated with:
```swift
let player = SimpleAudioPlayer()
let audioItem = DefaultAudioItem(audioUrl: "someUrl", sourceType: .stream)
player.load(item: audioItem)
player.next() // Increments the queue, and loads the next item.
player.previous() // Decrements the queue, and loads the previous item.
player.jumpToItem(atIndex:) // Jumps to a certain item and loads that item.
```
**NOTE**: Do not use `AudioPlayer` directly. Use one of the above types.
##### Manipulating the queue
```swift
player.removeItem(at:) // Remove a specific item from the queue.
player.removeUpcomingItems() // Remove all items in nextItems.
```
#### States
The `AudioPlayer` has a `state` property, to make it easier to determine appropriate actions. The different states:
+ **idle**: The player is doing nothing, no item is set as current. This is the default state.
+ **ready**: The player has its current item set and is ready to start loading for playback. This is when you can call `play()` if you supplied `playWhenReady=false` when calling `load(item:playWhenReady)`.
+ **loading**: The player is loading the track and will start playback soon.
+ **playing**: The player is playing.
+ **paused**: The player is paused.
#### Queue
The `QueuedAudioPlayer` maintains a queue of audio tracks.
The arrangement of the tracks are: [Previous]-[Current]-[Next].
When a track is done playing, the player will load the next track and update the queue, as long as `automaticallyPlayNextSong` is `true` (This is by default).
Items can be added to the queue by calling `player.add(item:)` or `player.add(items:)`.
Use `removeItem(atIndex:)` and `moveItem(fromIndex:toIndex:)` to manipulate the queue.
The queue can be navigated by using `next()`, `previous()` and `jumpToItem(atIndex:)`
### Configuring the AudioPlayer
Current options for configuring the `AudioPlayer`:
- `bufferDuration`: The amount of seconds to be buffered by the player.
- `timeEventFrequency`: How often the player should call the delegate with time progress events.
- `automaticallyWaitsToMinimizeStalling`: Indicates whether the player should automatically delay playback in order to minimize stalling.
- `volume`
- `isMuted`
- `rate`
- `audioTimePitchAlgorithm`: This value decides the `AVAudioTimePitchAlgorithm` used for each `AudioItem`. Implement `TimePitching` in your `AudioItem`-subclass to override individually for each `AudioItem`.
### Audio Session
Remember to activate an audio session with an appropriate category for your app. This can be done with `AudioSessionCategory`:
Remember to activate an audio session with an appropriate category for your app. This can be done with `AudioSessionController`:
```swift
try? AudioSessionController.set(category: .playback)
//...
@@ -74,22 +89,23 @@ try? AudioSessionController.set(category: .playback)
try? AudioSessionController.activateSession()
```
If you want audio to continue playing when the app is inactive, remember to activate background audio:
**Important**: If you want audio to continue playing when the app is inactive, remember to activate background audio:
App Settings -> Capabilities -> Background Modes -> Check 'Audio, AirPlay, and Picture in Picture'.
#### Interruptions
If you are using the AudioSessionController for setting up the audio session, you can use it to handle interruptions too.
If you are using the `AudioSessionController` for setting up the audio session, you can use it to handle interruptions too.
Implement `AudioSessionControllerDelegate` and you will be notified by `handleInterruption(type: AVAudioSessionInterruptionType)`.
If you are storing progress for playback time on items when the app quits, it can be a good idea to do it on interruptions as well.
To disable interruption notifcations set `isObservingForInterruptions` to `false`.
### Now Playing Info
The `AudioPlayer` will automatically update the `MPNowPlayingInfoCenter` with artist, title, album, artwork, time if the passed in `AudioItem` supports this.
If you need to set additional properties for some items use `AudioPlayer.add(property:)`. Available properties can be found in `NowPlayingInfoProperty`.
The `AudioPlayer` will automatically update the `MPNowPlayingInfoCenter` with artist, title, album, artwork and time if the passed in `AudioItem` supports this. This functionality can be turned off by setting `automaticallyUpdateNowPlayingInfo` to `false`.
If you need to set additional properties for some items, access the player's `NowPlayingInfoController` and call `set(keyValue:)`. Available properties can be found in `NowPlayingInfoProperty`.
### Remote Commands
**First** go to App Settings -> Capabilites -> Background Modes -> Check 'Remote notifications'
The player will handle remote commands received from `MPRemoteCommandCenter`'s shared instance, enabled by:
To enable remote commands for the player you need to populate the RemoteCommands array for the player:
```swift
audioPlayer.remoteCommands = [
.play,
@@ -98,20 +114,20 @@ audioPlayer.remoteCommands = [
.skipBackward(intervals: [30]),
]
```
These commands will be activated for each `AudioItem`. If you need some audio items to have different commands, implement `RemoteCommandable` in a custom `AudioItem`-subclass. These commands will override the commands found in `AudioPlayer.remoteCommands` so make sure to supply all commands you need for that particular `AudioItem`.
These commands will be activated for each `AudioItem`. If you need some audio items to have different commands, implement `RemoteCommandable`. These commands will override the commands found in `AudioPlayer.remoteCommands` so make sure to supply all commands you need for that particular `AudioItem`.
#### Custom handlers for remote commands
To supply custom handlers for your remote commands, just override the handlers contained in the player's `RemoteCommandController`:
```swift
let player = QueuedAudioPlayer()
player.remoteCommandController.handlePlayCommand = { (event) in
// Handle remote command here.
}
```
All available overrides can be found by looking at `RemoteCommandController`.
**Remember** to go to App Settings -> Capabilites -> Background Modes -> Check 'Remote notifications'
## Configuration
Currently some configuration options are supported:
+ `automaticallyWaitsToMinimizeStalling`: Whether the player should delay playback start to minimize stalling. If you are streaming large audio files and playback start is slow, it can help to set this to `false`. Default is `true`.
+ `bufferDuration`: The amount of seconds to be buffered by the player. Does not have any effect if `automaticallyWaitsToMinimizeStalling` is set to `true`.
+ `timeEventFrequency`: This decides how ofen the delegate should be notified that a time unit elapsed in the playback.
+ `volume`: The volume of the player. From 0.0 to 1.0.
+ `automaticallyUpdateNowPlayingInfo`: If you want to handle updating of the `MPNowPlayingInfoCenter` yourself, set this to `false`. Default is `true`.
### Start playback from a certain point in time
Make your `AudioItem`-subclass conform to `InitialTiming` to be able to start playback from a certain time.
## Author
+1 -1
View File
@@ -8,7 +8,7 @@
Pod::Spec.new do |s|
s.name = 'SwiftAudio'
s.version = '0.3.3'
s.version = '0.6.2'
s.summary = 'Easy audio streaming for iOS'
# This description is used to generate tags and improve search results.

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