//
//  Copyright (c) 2014-2016 Apple Inc. All rights reserved.
//

#import <XCTest/XCTestCase.h>
#import <XCTest/XCTWaiter.h>

@class XCTestExpectation;

NS_ASSUME_NONNULL_BEGIN

/*!
 * @category AsynchronousTesting
 *
 * @discussion
 * This category introduces support for asynchronous testing in XCTestCase. The mechanism
 * allows you to specify one or more "expectations" that will occur asynchronously
 * as a result of actions in the test. Once all expectations have been set, a "wait"
 * API is called that will block execution of subsequent test code until all expected
 * conditions have been fulfilled or a timeout occurs.
 */
@interface XCTestCase (AsynchronousTesting) <XCTWaiterDelegate>

/*!
 * @method -expectationWithDescription:
 *
 * @param description
 * This string will be displayed in the test log to help diagnose failures.
 *
 * @discussion
 * Creates and returns an expectation associated with the test case.
 */
- (XCTestExpectation *)expectationWithDescription:(NSString *)description XCT_WARN_UNUSED;

/*!
 * @typedef XCWaitCompletionHandler
 * A block to be invoked when a call to -waitForExpectationsWithTimeout:handler: times out or has
 * had all associated expectations fulfilled.
 *
 * @param error
 * If the wait timed out or a failure was raised while waiting, the error's code
 * will specify the type of failure. Otherwise error will be nil.
 */
typedef void (^XCWaitCompletionHandler)(NSError * __nullable error);

/*!
 * @method -waitForExpectationsWithTimeout:handler:
 *
 * @param timeout
 * The amount of time within which all expectations must be fulfilled.
 *
 * @param handler
 * If provided, the handler will be invoked both on timeout or fulfillment of all
 * expectations. Timeout is always treated as a test failure.
 *
 * @discussion
 * -waitForExpectationsWithTimeout:handler: creates a point of synchronization in the flow of a
 * test. Only one -waitForExpectationsWithTimeout:handler: can be active at any given time, but
 * multiple discrete sequences of { expectations -> wait } can be chained together.
 *
 * -waitForExpectationsWithTimeout:handler: runs the run loop while handling events until all expectations
 * are fulfilled or the timeout is reached. Clients should not manipulate the run
 * loop while using this API.
 */
- (void)waitForExpectationsWithTimeout:(NSTimeInterval)timeout handler:(nullable XCWaitCompletionHandler)handler;

/*!
 * @method -waitForExpectations:timeout:
 * Wait on a group of expectations for up to the specified timeout. May return early based on fulfillment
 * of the waited on expectations.
 */
- (void)waitForExpectations:(NSArray<XCTestExpectation *> *)expectations timeout:(NSTimeInterval)seconds;

/*!
 * @method -waitForExpectations:timeout:enforceOrder:
 * Wait on expectations and specify whether they must be fulfilled in the given order. Expectations
 * can only appear in the list once.
 */
- (void)waitForExpectations:(NSArray<XCTestExpectation *> *)expectations timeout:(NSTimeInterval)seconds enforceOrder:(BOOL)enforceOrderOfFulfillment;

#pragma mark Convenience APIs

/*!
 * @method -keyValueObservingExpectationForObject:keyPath:expectedValue:
 *
 * @discussion
 * A convenience method for asynchronous tests that use Key Value Observing to detect changes
 * to values on an object. This variant takes an expected value and observes changes on the object
 * until the keyPath's value matches the expected value using -[NSObject isEqual:]. If
 * other comparisions are needed, use the variant below that takes a handler block.
 *
 * @param objectToObserve
 * The object to observe.
 *
 * @param keyPath
 * The key path to observe.
 *
 * @param expectedValue
 * Expected value of the keyPath for the object. The expectation will fulfill itself when the
 * keyPath is equal, as tested using -[NSObject isEqual:]. If nil, the expectation will be
 * fulfilled by the first change to the key path of the observed object.
 *
 * @return
 * Creates and returns an expectation associated with the test case.
 */
- (XCTestExpectation *)keyValueObservingExpectationForObject:(id)objectToObserve keyPath:(NSString *)keyPath expectedValue:(nullable id)expectedValue;

/*!
 * @typedef
 * A block to be invoked when a change is observed for the keyPath of the observed object.
 *
 * @param observedObject
 * The observed object, provided to avoid block capture issues.
 *
 * @param change
 * The KVO change dictionary.
 *
 * @return
 * Return YES if the expectation is fulfilled, NO if it is not.
 */
typedef BOOL (^XCKeyValueObservingExpectationHandler)(id observedObject, NSDictionary *change);

/*!
 * @method -keyValueObservingExpectationForObject:keyPath:handler:
 *
 * @discussion
 * Variant of the convenience for tests that use Key Value Observing. Takes a handler
 * block instead of an expected value. Every KVO change will run the handler block until
 * it returns YES (or the wait times out). Returning YES from the block will fulfill the
 * expectation. XCTAssert and related APIs can be used in the block to report a failure.
 *
 * @param objectToObserve
 * The object to observe.
 *
 * @param keyPath
 * The key path to observe.
 *
 * @param handler
 * Optional handler, /see XCKeyValueObservingExpectationHandler. If not provided, the expectation will
 * be fulfilled by the first change to the key path of the observed object.
 *
 * @return
 * Creates and returns an expectation associated with the test case.
 */
- (XCTestExpectation *)keyValueObservingExpectationForObject:(id)objectToObserve keyPath:(NSString *)keyPath handler:(nullable XCKeyValueObservingExpectationHandler)handler;

/*!
 * @typedef
 * A block to be invoked when a notification matching the specified name is observed
 * from the object.
 *
 * @param notification
 * The notification object.
 *
 * @return
 * Return YES if the expectation is fulfilled, NO if it is not.
 */
typedef BOOL (^XCNotificationExpectationHandler)(NSNotification *notification);

/*!
 * @method -expectationForNotification:object:handler:
 *
 * @discussion
 * A convenience method for asynchronous tests that observe NSNotifications.
 *
 * @param notificationName
 * The notification to register for.
 *
 * @param objectToObserve
 * The object to observe.
 *
 * @param handler
 * Optional handler, /see XCNotificationExpectationHandler. If not provided, the expectation
 * will be fulfilled by the first notification matching the specified name from the
 * observed object.
 *
 * @return
 * Creates and returns an expectation associated with the test case.
 */
- (XCTestExpectation *)expectationForNotification:(NSString *)notificationName object:(nullable id)objectToObserve handler:(nullable XCNotificationExpectationHandler)handler;

/*!
 * @typedef
 * Handler called when evaluating the predicate against the object returns true. If the handler is not
 * provided the first successful evaluation will fulfill the expectation. If provided, the handler will
 * be queried each time the notification is received to determine whether the expectation should be fulfilled
 * or not.
 */
typedef BOOL (^XCPredicateExpectationHandler)();

/*!
 * @method -expectationForPredicate:evaluatedWithObject:handler:
 * Creates an expectation that is fulfilled if the predicate returns true when evaluated with the given
 * object. The expectation periodically evaluates the predicate and also may use notifications or other
 * events to optimistically re-evaluate.
 */
- (XCTestExpectation *)expectationForPredicate:(NSPredicate *)predicate evaluatedWithObject:(id)object handler:(nullable XCPredicateExpectationHandler)handler;

@end

NS_ASSUME_NONNULL_END
