mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
9f94329397
2570 changed files with 11121 additions and 188936 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -31,7 +31,6 @@ Project.xcodeproj/*
|
|||
Watch/Watch.xcodeproj/*
|
||||
AppBundle.xcworkspace/*
|
||||
*.xcworkspace
|
||||
tools/buck
|
||||
*.xcodeproj
|
||||
!*_Xcode.xcodeproj
|
||||
.idea
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ text_section_items = [
|
|||
"__text",
|
||||
]
|
||||
|
||||
text_section_rename_linker_flags = ["-Wl,-rename_section,__TEXT,%s,__MEXT,%s" % (name, name) for name in text_section_items] + ["-Wl,-segprot,__MEXT,rx,rx"]
|
||||
text_section_rename_linker_flags = [] #["-Wl,-rename_section,__TEXT,%s,__MEXT,%s" % (name, name) for name in text_section_items] + ["-Wl,-segprot,__MEXT,rx,rx"]
|
||||
|
||||
section_rename_linker_flags = text_section_rename_linker_flags
|
||||
|
||||
|
|
|
|||
|
|
@ -298,6 +298,7 @@ def watch_extension_info_plist_substitutions():
|
|||
"CURRENT_PROJECT_VERSION": "1",
|
||||
"BUILD_NUMBER": get_build_number(),
|
||||
"PRODUCT_BUNDLE_SHORT_VERSION": get_short_version(),
|
||||
"MinimumOSVersion": "5.0",
|
||||
}
|
||||
return substitutions
|
||||
|
||||
|
|
@ -312,5 +313,6 @@ def watch_info_plist_substitutions():
|
|||
"CURRENT_PROJECT_VERSION": "1",
|
||||
"BUILD_NUMBER": get_build_number(),
|
||||
"PRODUCT_BUNDLE_SHORT_VERSION": get_short_version(),
|
||||
"MinimumOSVersion": "5.0",
|
||||
}
|
||||
return substitutions
|
||||
|
|
|
|||
2
Makefile
2
Makefile
|
|
@ -3,7 +3,7 @@
|
|||
include Utils.makefile
|
||||
|
||||
BUCK_OPTIONS=\
|
||||
--config custom.appVersion="5.12.1" \
|
||||
--config custom.appVersion="5.12.2" \
|
||||
--config custom.developmentCodeSignIdentity="${DEVELOPMENT_CODE_SIGN_IDENTITY}" \
|
||||
--config custom.distributionCodeSignIdentity="${DISTRIBUTION_CODE_SIGN_IDENTITY}" \
|
||||
--config custom.developmentTeam="${DEVELOPMENT_TEAM}" \
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>5.12.1</string>
|
||||
<string>$(PRODUCT_BUNDLE_SHORT_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${BUILD_NUMBER}</string>
|
||||
<key>NSExtension</key>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>5.12.1</string>
|
||||
<string>$(PRODUCT_BUNDLE_SHORT_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${BUILD_NUMBER}</string>
|
||||
<key>NSExtension</key>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
@interface NotificationServiceImpl : NSObject
|
||||
|
||||
- (instancetype)initWithCountIncomingMessage:(void (^)(NSString *, int64_t, DeviceSpecificEncryptionParameters *, int64_t, int32_t))countIncomingMessage isLocked:(bool (^)(NSString *))isLocked lockedMessageText:(NSString *(^)(NSString *))lockedMessageText;
|
||||
- (instancetype)initWithSerialDispatch:(void (^)(dispatch_block_t))serialDispatch countIncomingMessage:(void (^)(NSString *, int64_t, DeviceSpecificEncryptionParameters *, int64_t, int32_t))countIncomingMessage isLocked:(bool (^)(NSString *))isLocked lockedMessageText:(NSString *(^)(NSString *))lockedMessageText;
|
||||
|
||||
- (void)updateUnreadCount:(int32_t)unreadCount;
|
||||
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler;
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ static void reportMemory() {
|
|||
#endif
|
||||
|
||||
@interface NotificationServiceImpl () {
|
||||
void (^_serialDispatch)(dispatch_block_t);
|
||||
void (^_countIncomingMessage)(NSString *, int64_t, DeviceSpecificEncryptionParameters *, int64_t, int32_t);
|
||||
|
||||
NSString * _Nullable _rootPath;
|
||||
|
|
@ -70,13 +71,14 @@ static void reportMemory() {
|
|||
|
||||
@implementation NotificationServiceImpl
|
||||
|
||||
- (instancetype)initWithCountIncomingMessage:(void (^)(NSString *, int64_t, DeviceSpecificEncryptionParameters *, int64_t, int32_t))countIncomingMessage isLocked:(nonnull bool (^)(NSString * _Nonnull))isLocked lockedMessageText:(NSString *(^)(NSString *))lockedMessageText {
|
||||
- (instancetype)initWithSerialDispatch:(void (^)(dispatch_block_t))serialDispatch countIncomingMessage:(void (^)(NSString *, int64_t, DeviceSpecificEncryptionParameters *, int64_t, int32_t))countIncomingMessage isLocked:(nonnull bool (^)(NSString * _Nonnull))isLocked lockedMessageText:(NSString *(^)(NSString *))lockedMessageText {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
#if DEBUG
|
||||
reportMemory();
|
||||
#endif
|
||||
|
||||
_serialDispatch = [serialDispatch copy];
|
||||
_countIncomingMessage = [countIncomingMessage copy];
|
||||
|
||||
NSString *appBundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
|
||||
|
|
@ -127,27 +129,32 @@ static void reportMemory() {
|
|||
reportMemory();
|
||||
#endif
|
||||
|
||||
#ifdef __IPHONE_13_0
|
||||
if (_baseAppBundleId != nil) {
|
||||
BGAppRefreshTaskRequest *request = [[BGAppRefreshTaskRequest alloc] initWithIdentifier:[_baseAppBundleId stringByAppendingString:@".refresh"]];
|
||||
request.earliestBeginDate = nil;
|
||||
NSError *error = nil;
|
||||
[[BGTaskScheduler sharedScheduler] submitTaskRequest:request error:&error];
|
||||
if (error != nil) {
|
||||
NSLog(@"Error: %@", error);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
NSString *baseAppBundleId = _baseAppBundleId;
|
||||
void (^contentHandler)(UNNotificationContent *) = [_contentHandler copy];
|
||||
UNMutableNotificationContent *bestAttemptContent = _bestAttemptContent;
|
||||
NSNumber *updatedUnreadCount = updatedUnreadCount;
|
||||
|
||||
if (_bestAttemptContent && _contentHandler) {
|
||||
if (_updatedUnreadCount != nil) {
|
||||
int32_t unreadCount = (int32_t)[_updatedUnreadCount intValue];
|
||||
if (unreadCount > 0) {
|
||||
_bestAttemptContent.badge = @(unreadCount);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
#ifdef __IPHONE_13_0
|
||||
if (baseAppBundleId != nil && false) {
|
||||
BGAppRefreshTaskRequest *request = [[BGAppRefreshTaskRequest alloc] initWithIdentifier:[baseAppBundleId stringByAppendingString:@".refresh"]];
|
||||
request.earliestBeginDate = nil;
|
||||
NSError *error = nil;
|
||||
[[BGTaskScheduler sharedScheduler] submitTaskRequest:request error:&error];
|
||||
if (error != nil) {
|
||||
NSLog(@"Error: %@", error);
|
||||
}
|
||||
}
|
||||
_contentHandler(_bestAttemptContent);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (updatedUnreadCount != nil) {
|
||||
int32_t unreadCount = (int32_t)[updatedUnreadCount intValue];
|
||||
if (unreadCount > 0) {
|
||||
bestAttemptContent.badge = @(unreadCount);
|
||||
}
|
||||
}
|
||||
contentHandler(bestAttemptContent);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
|
||||
|
|
@ -219,13 +226,16 @@ static void reportMemory() {
|
|||
silent = [silentString intValue] != 0;
|
||||
}
|
||||
|
||||
NSString *attachmentDataString = decryptedPayload[@"attachb64"];
|
||||
NSData *attachmentData = nil;
|
||||
id parsedAttachment = nil;
|
||||
if ([attachmentDataString isKindOfClass:[NSString class]]) {
|
||||
attachmentData = parseBase64(attachmentDataString);
|
||||
if (attachmentData != nil) {
|
||||
parsedAttachment = parseAttachment(attachmentData);
|
||||
|
||||
if (_isLockedValue) {
|
||||
NSString *attachmentDataString = decryptedPayload[@"attachb64"];
|
||||
if ([attachmentDataString isKindOfClass:[NSString class]]) {
|
||||
attachmentData = parseBase64(attachmentDataString);
|
||||
if (attachmentData != nil) {
|
||||
parsedAttachment = parseAttachment(attachmentData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -317,11 +327,12 @@ static void reportMemory() {
|
|||
}
|
||||
_bestAttemptContent.title = title;
|
||||
if (_isLockedValue) {
|
||||
_bestAttemptContent.title = @"";
|
||||
_bestAttemptContent.subtitle = @"";
|
||||
if (_lockedMessageTextValue != nil) {
|
||||
_bestAttemptContent.body = _lockedMessageTextValue;
|
||||
} else {
|
||||
_bestAttemptContent.body = @"You have a new message";
|
||||
_bestAttemptContent.body = @"^You have a new message";
|
||||
}
|
||||
} else {
|
||||
_bestAttemptContent.subtitle = subtitle;
|
||||
|
|
@ -334,44 +345,52 @@ static void reportMemory() {
|
|||
if (_lockedMessageTextValue != nil) {
|
||||
_bestAttemptContent.body = _lockedMessageTextValue;
|
||||
} else {
|
||||
_bestAttemptContent.body = @"You have a new message";
|
||||
_bestAttemptContent.body = @"^You have a new message";
|
||||
}
|
||||
} else {
|
||||
_bestAttemptContent.body = alert;
|
||||
}
|
||||
}
|
||||
|
||||
NSString *threadIdString = aps[@"thread-id"];
|
||||
if ([threadIdString isKindOfClass:[NSString class]]) {
|
||||
_bestAttemptContent.threadIdentifier = threadIdString;
|
||||
if (_isLockedValue) {
|
||||
_bestAttemptContent.threadIdentifier = @"locked";
|
||||
} else {
|
||||
NSString *threadIdString = aps[@"thread-id"];
|
||||
if ([threadIdString isKindOfClass:[NSString class]]) {
|
||||
_bestAttemptContent.threadIdentifier = threadIdString;
|
||||
}
|
||||
}
|
||||
NSString *soundString = aps[@"sound"];
|
||||
if ([soundString isKindOfClass:[NSString class]]) {
|
||||
_bestAttemptContent.sound = [UNNotificationSound soundNamed:soundString];
|
||||
}
|
||||
NSString *categoryString = aps[@"category"];
|
||||
if ([categoryString isKindOfClass:[NSString class]]) {
|
||||
_bestAttemptContent.categoryIdentifier = categoryString;
|
||||
if (peerId != 0 && messageId != 0 && parsedAttachment != nil && attachmentData != nil) {
|
||||
userInfo[@"peerId"] = @(peerId);
|
||||
userInfo[@"messageId.namespace"] = @(0);
|
||||
userInfo[@"messageId.id"] = @(messageId);
|
||||
|
||||
userInfo[@"media"] = [attachmentData base64EncodedStringWithOptions:0];
|
||||
|
||||
if (isExpandableMedia) {
|
||||
if ([categoryString isEqualToString:@"r"]) {
|
||||
_bestAttemptContent.categoryIdentifier = @"withReplyMedia";
|
||||
} else if ([categoryString isEqualToString:@"m"]) {
|
||||
_bestAttemptContent.categoryIdentifier = @"withMuteMedia";
|
||||
if (_isLockedValue) {
|
||||
_bestAttemptContent.categoryIdentifier = @"locked";
|
||||
} else {
|
||||
NSString *categoryString = aps[@"category"];
|
||||
if ([categoryString isKindOfClass:[NSString class]]) {
|
||||
_bestAttemptContent.categoryIdentifier = categoryString;
|
||||
if (peerId != 0 && messageId != 0 && parsedAttachment != nil && attachmentData != nil) {
|
||||
userInfo[@"peerId"] = @(peerId);
|
||||
userInfo[@"messageId.namespace"] = @(0);
|
||||
userInfo[@"messageId.id"] = @(messageId);
|
||||
|
||||
userInfo[@"media"] = [attachmentData base64EncodedStringWithOptions:0];
|
||||
|
||||
if (isExpandableMedia) {
|
||||
if ([categoryString isEqualToString:@"r"]) {
|
||||
_bestAttemptContent.categoryIdentifier = @"withReplyMedia";
|
||||
} else if ([categoryString isEqualToString:@"m"]) {
|
||||
_bestAttemptContent.categoryIdentifier = @"withMuteMedia";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (accountInfos.accounts.count > 1) {
|
||||
if (_bestAttemptContent.title.length != 0 && account.peerName.length != 0) {
|
||||
_bestAttemptContent.title = [NSString stringWithFormat:@"%@ → %@", _bestAttemptContent.title, account.peerName];
|
||||
|
||||
if (accountInfos.accounts.count > 1) {
|
||||
if (_bestAttemptContent.title.length != 0 && account.peerName.length != 0) {
|
||||
_bestAttemptContent.title = [NSString stringWithFormat:@"%@ → %@", _bestAttemptContent.title, account.peerName];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -402,9 +421,11 @@ static void reportMemory() {
|
|||
} else {
|
||||
BuildConfig *buildConfig = [[BuildConfig alloc] initWithBaseAppBundleId:_baseAppBundleId];
|
||||
|
||||
void (^serialDispatch)(dispatch_block_t) = _serialDispatch;
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
_cancelFetch = fetchImage(buildConfig, accountInfos.proxy, account, inputFileLocation, fileDatacenterId, ^(NSData * _Nullable data) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
serialDispatch(^{
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
if (strongSelf == nil) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,35 +1,52 @@
|
|||
import Foundation
|
||||
import UserNotifications
|
||||
import SwiftSignalKit
|
||||
|
||||
private let queue = Queue()
|
||||
|
||||
@available(iOSApplicationExtension 10.0, *)
|
||||
@objc(NotificationService)
|
||||
final class NotificationService: UNNotificationServiceExtension {
|
||||
private let impl: NotificationServiceImpl
|
||||
private let impl: QueueLocalObject<NotificationServiceImpl>
|
||||
|
||||
override init() {
|
||||
var completion: ((Int32) -> Void)?
|
||||
self.impl = NotificationServiceImpl(countIncomingMessage: { rootPath, accountId, encryptionParameters, peerId, messageId in
|
||||
SyncProviderImpl.addIncomingMessage(withRootPath: rootPath, accountId: accountId, encryptionParameters: encryptionParameters, peerId: peerId, messageId: messageId, completion: { count in
|
||||
completion?(count)
|
||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||
var completion: ((Int32) -> Void)?
|
||||
let impl = NotificationServiceImpl(serialDispatch: { f in
|
||||
queue.async {
|
||||
f()
|
||||
}
|
||||
}, countIncomingMessage: { rootPath, accountId, encryptionParameters, peerId, messageId in
|
||||
SyncProviderImpl.addIncomingMessage(queue: queue, withRootPath: rootPath, accountId: accountId, encryptionParameters: encryptionParameters, peerId: peerId, messageId: messageId, completion: { count in
|
||||
completion?(count)
|
||||
})
|
||||
}, isLocked: { rootPath in
|
||||
return SyncProviderImpl.isLocked(withRootPath: rootPath)
|
||||
}, lockedMessageText: { rootPath in
|
||||
return SyncProviderImpl.lockedMessageText(withRootPath: rootPath)
|
||||
})
|
||||
}, isLocked: { rootPath in
|
||||
return SyncProviderImpl.isLocked(withRootPath: rootPath)
|
||||
}, lockedMessageText: { rootPath in
|
||||
return SyncProviderImpl.lockedMessageText(withRootPath: rootPath)
|
||||
|
||||
completion = { [weak impl] count in
|
||||
queue.async {
|
||||
impl?.updateUnreadCount(count)
|
||||
}
|
||||
}
|
||||
|
||||
return impl
|
||||
})
|
||||
|
||||
super.init()
|
||||
|
||||
completion = { [weak self] count in
|
||||
self?.impl.updateUnreadCount(count)
|
||||
}
|
||||
}
|
||||
|
||||
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||
self.impl.didReceive(request, withContentHandler: contentHandler)
|
||||
self.impl.with { impl in
|
||||
impl.didReceive(request, withContentHandler: contentHandler)
|
||||
}
|
||||
}
|
||||
|
||||
override func serviceExtensionTimeWillExpire() {
|
||||
self.impl.serviceExtensionTimeWillExpire()
|
||||
self.impl.with { impl in
|
||||
impl.serviceExtensionTimeWillExpire()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,17 +42,17 @@ enum SyncProviderImpl {
|
|||
}
|
||||
}
|
||||
|
||||
static func addIncomingMessage(withRootPath rootPath: String, accountId: Int64, encryptionParameters: DeviceSpecificEncryptionParameters, peerId: Int64, messageId: Int32, completion: @escaping (Int32) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
static func addIncomingMessage(queue: Queue, withRootPath rootPath: String, accountId: Int64, encryptionParameters: DeviceSpecificEncryptionParameters, peerId: Int64, messageId: Int32, completion: @escaping (Int32) -> Void) {
|
||||
queue.async {
|
||||
let _ = registeredTypes
|
||||
|
||||
let sharedBasePath = rootPath + "/accounts-metadata"
|
||||
let basePath = rootPath + "/" + accountRecordIdPathName(accountId) + "/postbox"
|
||||
|
||||
let sharedValueBox = SqliteValueBox(basePath: sharedBasePath + "/db", queue: Queue.mainQueue(), logger: ValueBoxLoggerImpl(), encryptionParameters: nil, disableCache: true, upgradeProgress: { _ in
|
||||
let sharedValueBox = SqliteValueBox(basePath: sharedBasePath + "/db", queue: queue, logger: ValueBoxLoggerImpl(), encryptionParameters: nil, disableCache: true, upgradeProgress: { _ in
|
||||
})
|
||||
|
||||
let valueBox = SqliteValueBox(basePath: basePath + "/db", queue: Queue.mainQueue(), logger: ValueBoxLoggerImpl(), encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: encryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: encryptionParameters.salt)!), disableCache: true, upgradeProgress: { _ in
|
||||
let valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, logger: ValueBoxLoggerImpl(), encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: encryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: encryptionParameters.salt)!), disableCache: true, upgradeProgress: { _ in
|
||||
})
|
||||
|
||||
let metadataTable = MessageHistoryMetadataTable(valueBox: valueBox, table: MessageHistoryMetadataTable.tableSpec(10))
|
||||
|
|
|
|||
|
|
@ -6,6 +6,16 @@ import BuildConfig
|
|||
class ShareRootController: UIViewController {
|
||||
private var impl: ShareRootControllerImpl?
|
||||
|
||||
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
|
||||
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
|
||||
|
||||
self.modalPresentationStyle = .fullScreen
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func loadView() {
|
||||
super.loadView()
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>5.12.1</string>
|
||||
<string>$(PRODUCT_BUNDLE_SHORT_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${BUILD_NUMBER}</string>
|
||||
<key>NSExtension</key>
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11134" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="ObA-dk-sSI">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11106"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Intent View Controller-->
|
||||
<scene sceneID="7MM-of-jgj">
|
||||
<objects>
|
||||
<viewController id="ObA-dk-sSI" customClass="IntentViewController" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="qkL-Od-lgU"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="n38-gi-rB5"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="zMn-AG-sqS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="150"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</view>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<size key="freeformSize" width="320" height="150"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="X47-rx-isc" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
<?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>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>SiriIntentsUI</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>104</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
<dict>
|
||||
<key>IntentsSupported</key>
|
||||
<array>
|
||||
<string>INSendMessageIntent</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>NSExtensionMainStoryboard</key>
|
||||
<string>MainInterface</string>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.intents-ui-service</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
//
|
||||
// IntentViewController.swift
|
||||
// SiriIntentsUI
|
||||
//
|
||||
// Created by Peter on 9/2/16.
|
||||
// Copyright © 2016 Telegram. All rights reserved.
|
||||
//
|
||||
|
||||
import IntentsUI
|
||||
|
||||
// As an example, this extension's Info.plist has been configured to handle interactions for INSendMessageIntent.
|
||||
// You will want to replace this or add other intents as appropriate.
|
||||
// The intents whose interactions you wish to handle must be declared in the extension's Info.plist.
|
||||
|
||||
// You can test this example integration by saying things to Siri like:
|
||||
// "Send a message using <myApp>"
|
||||
|
||||
class IntentViewController: UIViewController, INUIHostedViewControlling {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
// Do any additional setup after loading the view.
|
||||
}
|
||||
|
||||
override func didReceiveMemoryWarning() {
|
||||
super.didReceiveMemoryWarning()
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
// MARK: - INUIHostedViewControlling
|
||||
|
||||
// Prepare your view controller for the interaction to handle.
|
||||
func configure(with interaction: INInteraction!, context: INUIHostedViewContext, completion: ((CGSize) -> Void)!) {
|
||||
// Do configuration here, including preparing views and calculating a desired size for presentation.
|
||||
|
||||
if let completion = completion {
|
||||
completion(self.desiredSize)
|
||||
}
|
||||
}
|
||||
|
||||
var desiredSize: CGSize {
|
||||
//return self.extensionContext!.hostedViewMaximumAllowedSize
|
||||
return CGSize()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<?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>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.org.telegram.Telegram-iOS</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -189,7 +189,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>5.12.1</string>
|
||||
<string>$(PRODUCT_BUNDLE_SHORT_VERSION)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
|
|
|||
|
|
@ -4789,7 +4789,7 @@ Any member of this group will be able to see messages in the channel.";
|
|||
"Wallet.Info.Send" = "Send";
|
||||
"Wallet.Info.RefreshErrorTitle" = "No network";
|
||||
"Wallet.Info.RefreshErrorText" = "Couldn't refresh balance. Please make sure your internet connection is working and try again.";
|
||||
"Wallet.Info.RefreshErrorNetworkText" = "The wallet state can not be retrieved at this time. Please try again later.";
|
||||
"Wallet.Info.RefreshErrorNetworkText" = "Wallet state can not be retrieved at this time. Please try again later.";
|
||||
"Wallet.Info.UnknownTransaction" = "Empty Transaction";
|
||||
"Wallet.Info.TransactionTo" = "to";
|
||||
"Wallet.Info.TransactionFrom" = "from";
|
||||
|
|
@ -4820,7 +4820,7 @@ Any member of this group will be able to see messages in the channel.";
|
|||
"Wallet.Send.Balance" = "Balance: %@";
|
||||
"Wallet.Send.AmountText" = "Grams to send";
|
||||
"Wallet.Send.Confirmation" = "Confirmation";
|
||||
"Wallet.Send.ConfirmationText" = "Do you want to send **%1$@** Grams to\n%2$@?\n\nFees: ~%3$@ grams";
|
||||
"Wallet.Send.ConfirmationText" = "Do you want to send **%1$@** Grams to\n\n%2$@?\n\nBlockchain fees: ~%3$@ grams";
|
||||
"Wallet.Send.ConfirmationConfirm" = "Confirm";
|
||||
"Wallet.Send.Send" = "Send";
|
||||
"Wallet.Send.OwnAddressAlertTitle" = "Warning";
|
||||
|
|
@ -4828,8 +4828,9 @@ Any member of this group will be able to see messages in the channel.";
|
|||
"Wallet.Send.OwnAddressAlertProceed" = "Proceed";
|
||||
"Wallet.Send.TransactionInProgress" = "Please wait until the current transaction is completed.";
|
||||
"Wallet.Send.SyncInProgress" = "Please wait while the wallet finishes syncing with the TON Blockchain.";
|
||||
"Wallet.Settings.Title" = "Wallet Settings";
|
||||
"Wallet.Settings.Configuration" = "Configuration";
|
||||
"Wallet.Settings.Title" = "Settings";
|
||||
"Wallet.Settings.Configuration" = "Server Settings";
|
||||
"Wallet.Settings.ConfigurationInfo" = "Advanced Settings";
|
||||
"Wallet.Settings.BackupWallet" = "Backup Wallet";
|
||||
"Wallet.Settings.DeleteWallet" = "Delete Wallet";
|
||||
"Wallet.Settings.DeleteWalletInfo" = "This will disconnect the wallet from this app. You will be able to restore your wallet using 24 secret words – or import another wallet.\n\nGram Wallets are located in the decentralized TON Blockchain. If you want a wallet to be deleted, simply transfer all the grams from it and leave it empty.";
|
||||
|
|
@ -4888,7 +4889,7 @@ Any member of this group will be able to see messages in the channel.";
|
|||
"Wallet.TransactionInfo.OtherFeeInfoUrl" = "Blockchain validators collect a tiny fee for processing your decentralized transactions. [More info]()";
|
||||
"AppWallet.TransactionInfo.FeeInfoURL" = "https://telegram.org/wallet/fee";
|
||||
"Wallet.WordCheck.Title" = "Test Time!";
|
||||
"Wallet.WordCheck.Text" = "Let’s check that you wrote them down correctly. Please enter words\n**%1$@**, **%2$@** and **%3$@** below:";
|
||||
"Wallet.WordCheck.Text" = "Let’s check that you wrote them down correctly. Please enter the words\n**%1$@**, **%2$@** and **%3$@**";
|
||||
"Wallet.WordCheck.Continue" = "Continue";
|
||||
"Wallet.WordCheck.IncorrectHeader" = "Incorrect words!";
|
||||
"Wallet.WordCheck.IncorrectText" = "The secret words you have entered do not match the ones in the list.";
|
||||
|
|
@ -4924,8 +4925,23 @@ Any member of this group will be able to see messages in the channel.";
|
|||
"Conversation.WalletRequiredNotNow" = "Not Now";
|
||||
"Conversation.WalletRequiredSetup" = "Set Up";
|
||||
|
||||
"Wallet.Configuration.Title" = "Configuration";
|
||||
"Wallet.Configuration.Title" = "Server Settings";
|
||||
"Wallet.Configuration.Apply" = "Save";
|
||||
"Wallet.Configuration.SourceHeader" = "SOURCE";
|
||||
"Wallet.Configuration.SourceURL" = "URL";
|
||||
"Wallet.Configuration.SourceJSON" = "JSON";
|
||||
"Wallet.Configuration.SourceInfo" = "Using a different configuration allows you to change Lite Server addresses.";
|
||||
"Wallet.Configuration.BlockchainIdHeader" = "BLOCKCHAIN ID";
|
||||
"Wallet.Configuration.BlockchainIdPlaceholder" = "Blockchain ID";
|
||||
"Wallet.Configuration.BlockchainIdInfo" = "This setting is for developers. Change it only if you are working on creating your own TON network.";
|
||||
"Wallet.Configuration.ApplyErrorTitle" = "Error";
|
||||
"Wallet.Configuration.ApplyErrorTextURLInvalid" = "The URL you have entered is invalid. Please try again.";
|
||||
"Wallet.Configuration.ApplyErrorTextURLUnreachable" = "There was an error while downloading configuration from %@\nPlease try again.";
|
||||
"Wallet.Configuration.ApplyErrorTextURLInvalidData" = "This blockchain configuration is invalid. Please try again.";
|
||||
"Wallet.Configuration.ApplyErrorTextJSONInvalidData" = "This blockchain configuration is invalid. Please try again.";
|
||||
"Wallet.Configuration.BlockchainNameChangedTitle" = "Warning";
|
||||
"Wallet.Configuration.BlockchainNameChangedText" = "Are you sure you want to change the blockchain ID? You don't need this unless you're testing your own TON network.\n\nIf you proceed, you will need to reconnect your wallet using 24 secret words.";
|
||||
"Wallet.Configuration.BlockchainNameChangedProceed" = "Proceed";
|
||||
|
||||
"Wallet.CreateInvoice.Title" = "Create Invoice";
|
||||
|
||||
|
|
@ -5049,3 +5065,5 @@ Any member of this group will be able to see messages in the channel.";
|
|||
"WebBrowser.InAppSafari" = "In-App Safari";
|
||||
|
||||
"Widget.ApplicationLocked" = "Unlock the app to use the widget";
|
||||
|
||||
"Group.ErrorSupergroupConversionNotPossible" = "Sorry, you are a member of too many groups and channels. Please leave some before creating a new one.";
|
||||
|
|
|
|||
|
|
@ -80,13 +80,16 @@ apple_library(
|
|||
],
|
||||
deps = [
|
||||
"//submodules/WalletUI:WalletUI",
|
||||
"//submodules/WalletCore:WalletCore",
|
||||
"//submodules/BuildConfig:BuildConfig",
|
||||
"//submodules/OverlayStatusController:OverlayStatusController",
|
||||
]
|
||||
+ framework_binary_dependencies(framework_dependencies),
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
|
||||
"$SDKROOT/System/Library/Frameworks/VideoToolbox.framework",
|
||||
"$SDKROOT/System/Library/Frameworks/AVFoundation.framework",
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -53,10 +53,10 @@
|
|||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSFaceIDUsageDescription</key>
|
||||
<string>For better security, please allow TON Wallet to use your Face ID to authenticate payments.</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Please allow TON Wallet access to your camera for scanning QR codes.</string>
|
||||
<key>NSFaceIDUsageDescription</key>
|
||||
<string>For better security, please allow TON Wallet to use your Face ID to authenticate payments.</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Please allow TON Wallet access to your Photo Stream in case you need to scan a QR code from a picture.</string>
|
||||
<key>UIDeviceFamily</key>
|
||||
|
|
@ -75,7 +75,7 @@
|
|||
<key>UIRequiresPersistentWiFi</key>
|
||||
<true/>
|
||||
<key>UIStatusBarStyle</key>
|
||||
<string>UIStatusBarStyleDefault</string>
|
||||
<string>UIStatusBarStyleLightContent</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
|
|
|
|||
|
|
@ -30,6 +30,14 @@
|
|||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</view>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="Wallet/SplashCornerL" translatesAutoresizingMaskIntoConstraints="NO" id="4bZ-PB-VJX">
|
||||
<rect key="frame" x="0.0" y="260" width="10" height="10"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
</imageView>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="Wallet/SplashCornerR" translatesAutoresizingMaskIntoConstraints="NO" id="K7L-Vs-8lO">
|
||||
<rect key="frame" x="404" y="260" width="10" height="10"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
</view>
|
||||
|
|
@ -39,4 +47,8 @@
|
|||
<point key="canvasLocation" x="-137.68115942028987" y="-95.089285714285708"/>
|
||||
</navigationController>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="Wallet/SplashCornerL" width="10" height="10"/>
|
||||
<image name="Wallet/SplashCornerR" width="10" height="10"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
|
|
|||
63
Wallet/README.md
Normal file
63
Wallet/README.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# Test Gram Wallet (iOS)
|
||||
|
||||
This is the source code and build instructions for a TON Testnet Wallet implementation for iOS.
|
||||
|
||||
1. Install Xcode 11.1
|
||||
```
|
||||
https://apps.apple.com/ae/app/xcode/id497799835?mt=12
|
||||
```
|
||||
|
||||
Make sure to launch Xcode at least once and set up command-line tools paths (Xcode — Preferences — Locations — Command Line Tools)
|
||||
|
||||
2. Install Homebrew
|
||||
|
||||
```
|
||||
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
|
||||
```
|
||||
3. Install the required tools
|
||||
|
||||
```
|
||||
brew tap AdoptOpenJDK/openjdk
|
||||
brew cask install adoptopenjdk8
|
||||
brew install cmake ant
|
||||
```
|
||||
|
||||
4. Build Buck
|
||||
|
||||
```
|
||||
mkdir -p $HOME/buck_source
|
||||
cd tools/buck
|
||||
sh ./prepare_buck_source.sh $HOME/buck_source
|
||||
```
|
||||
|
||||
5. Now you can build Wallet application (IPA)
|
||||
|
||||
Note:
|
||||
It is recommended to use an artifact cache to optimize build speed. Prepend any of the following commands with
|
||||
```
|
||||
BUCK_DIR_CACHE="path/to/existing/directory"
|
||||
```
|
||||
|
||||
```
|
||||
BUCK="$HOME/buck_source/buck/buck-out/gen/programs/buck.pex" \
|
||||
BUILD_NUMBER=30 \
|
||||
DISTRIBUTION_CODE_SIGN_IDENTITY="iPhone Distribution: XXXXXXX (XXXXXXXXXX)" \
|
||||
DEVELOPMENT_TEAM="XXXXXXXXXX" WALLET_BUNDLE_ID="wallet.bundle.id" \
|
||||
WALLET_DISTRIBUTION_PROVISIONING_PROFILE_APP="wallet distribution provisioning profile name" \
|
||||
CODESIGNING_SOURCE_DATA_PATH="$HOME/wallet_codesigning" \
|
||||
sh Wallet/example_wallet_env.sh make -f Wallet.makefile wallet_app
|
||||
```
|
||||
|
||||
6. If needed, generate Xcode project
|
||||
```
|
||||
BUCK="$HOME/buck_source/buck/buck-out/gen/programs/buck.pex" \
|
||||
BUILD_NUMBER=30 \
|
||||
DEVELOPMENT_CODE_SIGN_IDENTITY="iPhone Developer: XXXXXXX (XXXXXXXXXX)" \
|
||||
DISTRIBUTION_CODE_SIGN_IDENTITY="iPhone Distribution: XXXXXXX (XXXXXXXXXX)" \
|
||||
DEVELOPMENT_TEAM="XXXXXXXXXX" WALLET_BUNDLE_ID="wallet.bundle.id" \
|
||||
WALLET_DEVELOPMENT_PROVISIONING_PROFILE_APP="wallet development provisioning profile name" \
|
||||
WALLET_DISTRIBUTION_PROVISIONING_PROFILE_APP="wallet distribution provisioning profile name" \
|
||||
CODESIGNING_SOURCE_DATA_PATH="$HOME/wallet_codesigning" \
|
||||
sh Wallet/example_wallet_env.sh make -f Wallet.makefile wallet_project
|
||||
```
|
||||
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import UIKit
|
||||
import Display
|
||||
import OverlayStatusController
|
||||
import SwiftSignalKit
|
||||
import BuildConfig
|
||||
import WalletUI
|
||||
|
|
@ -334,7 +335,10 @@ private final class WalletStorageInterfaceImpl: WalletStorageInterface {
|
|||
}
|
||||
|
||||
private final class WalletContextImpl: NSObject, WalletContext, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
|
||||
let storage: WalletStorageInterface
|
||||
var storage: WalletStorageInterface {
|
||||
return self.storageImpl
|
||||
}
|
||||
private let storageImpl: WalletStorageInterfaceImpl
|
||||
let tonInstance: TonInstance
|
||||
let keychain: TonKeychain
|
||||
let presentationData: WalletPresentationData
|
||||
|
|
@ -354,6 +358,23 @@ private final class WalletContextImpl: NSObject, WalletContext, UIImagePickerCon
|
|||
return .single(Data())
|
||||
}
|
||||
|
||||
func downloadFile(url: URL) -> Signal<Data, WalletDownloadFileError> {
|
||||
return download(url: url)
|
||||
|> mapError { _ in
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
|
||||
func updateResolvedWalletConfiguration(source: LocalWalletConfigurationSource, blockchainName: String, resolvedValue: String) -> Signal<Never, NoError> {
|
||||
return self.storageImpl.updateMergedLocalWalletConfiguration { configuration in
|
||||
var configuration = configuration
|
||||
configuration.configuration.source = source
|
||||
configuration.configuration.blockchainName = blockchainName
|
||||
configuration.resolved = ResolvedLocalWalletConfiguration(source: source, value: resolvedValue)
|
||||
return configuration
|
||||
}
|
||||
}
|
||||
|
||||
func presentNativeController(_ controller: UIViewController) {
|
||||
self.window.presentNative(controller)
|
||||
}
|
||||
|
|
@ -417,9 +438,9 @@ private final class WalletContextImpl: NSObject, WalletContext, UIImagePickerCon
|
|||
picker.presentingViewController?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
init(basePath: String, storage: WalletStorageInterfaceImpl, config: String, blockchainName: String, navigationBarTheme: NavigationBarTheme, window: Window1) {
|
||||
init(basePath: String, storage: WalletStorageInterfaceImpl, config: String, blockchainName: String, presentationData: WalletPresentationData, navigationBarTheme: NavigationBarTheme, window: Window1) {
|
||||
let _ = try? FileManager.default.createDirectory(at: URL(fileURLWithPath: basePath + "/keys"), withIntermediateDirectories: true, attributes: nil)
|
||||
self.storage = storage
|
||||
self.storageImpl = storage
|
||||
|
||||
self.window = window
|
||||
|
||||
|
|
@ -481,8 +502,42 @@ private final class WalletContextImpl: NSObject, WalletContext, UIImagePickerCon
|
|||
}
|
||||
})
|
||||
#endif
|
||||
|
||||
self.presentationData = presentationData
|
||||
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
||||
@objc(AppDelegate)
|
||||
final class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
var window: UIWindow?
|
||||
|
||||
private var mainWindow: Window1?
|
||||
private var walletContext: WalletContextImpl?
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
let statusBarHost = ApplicationStatusBarHost()
|
||||
let (window, hostView) = nativeWindowHostView()
|
||||
let mainWindow = Window1(hostView: hostView, statusBarHost: statusBarHost)
|
||||
self.mainWindow = mainWindow
|
||||
hostView.containerView.backgroundColor = UIColor.white
|
||||
self.window = window
|
||||
|
||||
let accentColor = UIColor(rgb: 0x007ee5)
|
||||
self.presentationData = WalletPresentationData(
|
||||
|
||||
let navigationBarTheme = NavigationBarTheme(
|
||||
buttonColor: accentColor,
|
||||
disabledButtonColor: UIColor(rgb: 0xd0d0d0),
|
||||
primaryTextColor: .black,
|
||||
backgroundColor: UIColor(rgb: 0xf7f7f7),
|
||||
separatorColor: UIColor(rgb: 0xb1b1b1),
|
||||
badgeBackgroundColor: UIColor(rgb: 0xff3b30),
|
||||
badgeStrokeColor: UIColor(rgb: 0xff3b30),
|
||||
badgeTextColor: .white
|
||||
)
|
||||
|
||||
let presentationData = WalletPresentationData(
|
||||
theme: WalletTheme(
|
||||
info: WalletInfoTheme(
|
||||
buttonBackgroundColor: accentColor,
|
||||
|
|
@ -566,37 +621,6 @@ private final class WalletContextImpl: NSObject, WalletContext, UIImagePickerCon
|
|||
)
|
||||
)
|
||||
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
||||
@objc(AppDelegate)
|
||||
final class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
var window: UIWindow?
|
||||
|
||||
private var mainWindow: Window1?
|
||||
private var walletContext: WalletContextImpl?
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
let statusBarHost = ApplicationStatusBarHost()
|
||||
let (window, hostView) = nativeWindowHostView()
|
||||
let mainWindow = Window1(hostView: hostView, statusBarHost: statusBarHost)
|
||||
self.mainWindow = mainWindow
|
||||
hostView.containerView.backgroundColor = UIColor.white
|
||||
self.window = window
|
||||
|
||||
let accentColor = UIColor(rgb: 0x007ee5)
|
||||
let navigationBarTheme = NavigationBarTheme(
|
||||
buttonColor: accentColor,
|
||||
disabledButtonColor: UIColor(rgb: 0xd0d0d0),
|
||||
primaryTextColor: .black,
|
||||
backgroundColor: UIColor(rgb: 0xf7f7f7),
|
||||
separatorColor: UIColor(rgb: 0xb1b1b1),
|
||||
badgeBackgroundColor: UIColor(rgb: 0xff3b30),
|
||||
badgeStrokeColor: UIColor(rgb: 0xff3b30),
|
||||
badgeTextColor: .white
|
||||
)
|
||||
|
||||
let navigationController = NavigationController(
|
||||
mode: .single,
|
||||
theme: NavigationControllerTheme(
|
||||
|
|
@ -608,6 +632,8 @@ final class AppDelegate: NSObject, UIApplicationDelegate {
|
|||
|
||||
mainWindow.viewController = navigationController
|
||||
|
||||
navigationController.setViewControllers([WalletApplicationSplashScreen(theme: presentationData.theme)], animated: false)
|
||||
|
||||
self.window?.makeKeyAndVisible()
|
||||
|
||||
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
|
||||
|
|
@ -634,7 +660,7 @@ final class AppDelegate: NSObject, UIApplicationDelegate {
|
|||
guard let parsedUrl = URL(string: url) else {
|
||||
return .complete()
|
||||
}
|
||||
return downloadFile(url: parsedUrl)
|
||||
return download(url: parsedUrl)
|
||||
|> retry(1.0, maxDelay: 5.0, onQueue: .mainQueue())
|
||||
|> mapToSignal { data -> Signal<(ResolvedLocalWalletConfiguration, String), NoError> in
|
||||
if let string = String(data: data, encoding: .utf8) {
|
||||
|
|
@ -666,13 +692,38 @@ final class AppDelegate: NSObject, UIApplicationDelegate {
|
|||
|
||||
let _ = (resolvedInitialConfig
|
||||
|> deliverOnMainQueue).start(next: { (initialResolvedConfig, initialConfigBlockchainName) in
|
||||
let walletContext = WalletContextImpl(basePath: documentsPath, storage: storage, config: initialResolvedConfig.value, blockchainName: initialConfigBlockchainName, navigationBarTheme: navigationBarTheme, window: mainWindow)
|
||||
let walletContext = WalletContextImpl(basePath: documentsPath, storage: storage, config: initialResolvedConfig.value, blockchainName: initialConfigBlockchainName, presentationData: presentationData, navigationBarTheme: navigationBarTheme, window: mainWindow)
|
||||
self.walletContext = walletContext
|
||||
|
||||
let _ = (updatedConfigValue
|
||||
|> deliverOnMainQueue).start(next: { resolved, blockchainName in
|
||||
let _ = walletContext.tonInstance.updateConfig(config: resolved.value, blockchainName: blockchainName).start()
|
||||
})
|
||||
let beginWithController: (ViewController) -> Void = { controller in
|
||||
navigationController.setViewControllers([controller], animated: false)
|
||||
|
||||
var previousBlockchainName = initialConfigBlockchainName
|
||||
|
||||
let _ = (updatedConfigValue
|
||||
|> deliverOnMainQueue).start(next: { resolved, blockchainName in
|
||||
let _ = walletContext.tonInstance.validateConfig(config: resolved.value, blockchainName: blockchainName).start(error: { _ in
|
||||
}, completed: {
|
||||
let _ = walletContext.tonInstance.updateConfig(config: resolved.value, blockchainName: blockchainName).start()
|
||||
|
||||
if previousBlockchainName != blockchainName {
|
||||
previousBlockchainName = blockchainName
|
||||
|
||||
let overlayController = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
|
||||
mainWindow.present(overlayController, on: .root)
|
||||
|
||||
let _ = (deleteAllLocalWalletsData(storage: walletContext.storage, tonInstance: walletContext.tonInstance)
|
||||
|> deliverOnMainQueue).start(error: { [weak overlayController] _ in
|
||||
overlayController?.dismiss()
|
||||
}, completed: { [weak overlayController] in
|
||||
overlayController?.dismiss()
|
||||
|
||||
navigationController.setViewControllers([WalletSplashScreen(context: walletContext, mode: .intro, walletCreatedPreloadState: nil)], animated: true)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
let _ = (combineLatest(queue: .mainQueue(),
|
||||
walletContext.storage.getWalletRecords(),
|
||||
|
|
@ -681,39 +732,32 @@ final class AppDelegate: NSObject, UIApplicationDelegate {
|
|||
|> deliverOnMainQueue).start(next: { records, publicKey in
|
||||
if let record = records.first {
|
||||
if let publicKey = publicKey {
|
||||
print("publicKey = \(publicKey.base64EncodedString())")
|
||||
if record.info.encryptedSecret.publicKey == publicKey {
|
||||
if record.exportCompleted {
|
||||
let _ = (walletAddress(publicKey: record.info.publicKey, tonInstance: walletContext.tonInstance)
|
||||
|> deliverOnMainQueue).start(next: { address in
|
||||
let infoScreen = WalletInfoScreen(context: walletContext, walletInfo: record.info, address: address, enableDebugActions: false)
|
||||
|
||||
navigationController.setViewControllers([infoScreen], animated: false)
|
||||
beginWithController(infoScreen)
|
||||
})
|
||||
} else {
|
||||
let createdScreen = WalletSplashScreen(context: walletContext, mode: .created(record.info, nil), walletCreatedPreloadState: nil)
|
||||
|
||||
navigationController.setViewControllers([createdScreen], animated: false)
|
||||
beginWithController(createdScreen)
|
||||
}
|
||||
} else {
|
||||
let splashScreen = WalletSplashScreen(context: walletContext, mode: .secureStorageReset(.changed), walletCreatedPreloadState: nil)
|
||||
|
||||
navigationController.setViewControllers([splashScreen], animated: false)
|
||||
beginWithController(splashScreen)
|
||||
}
|
||||
} else {
|
||||
let splashScreen = WalletSplashScreen(context: walletContext, mode: WalletSplashMode.secureStorageReset(.notAvailable), walletCreatedPreloadState: nil)
|
||||
|
||||
navigationController.setViewControllers([splashScreen], animated: false)
|
||||
beginWithController(splashScreen)
|
||||
}
|
||||
} else {
|
||||
if publicKey != nil {
|
||||
let splashScreen = WalletSplashScreen(context: walletContext, mode: .intro, walletCreatedPreloadState: nil)
|
||||
|
||||
navigationController.setViewControllers([splashScreen], animated: false)
|
||||
beginWithController(splashScreen)
|
||||
} else {
|
||||
let splashScreen = WalletSplashScreen(context: walletContext, mode: .secureStorageNotAvailable, walletCreatedPreloadState: nil)
|
||||
|
||||
navigationController.setViewControllers([splashScreen], animated: false)
|
||||
beginWithController(splashScreen)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -727,7 +771,7 @@ private enum DownloadFileError {
|
|||
case network
|
||||
}
|
||||
|
||||
private func downloadFile(url: URL) -> Signal<Data, DownloadFileError> {
|
||||
private func download(url: URL) -> Signal<Data, DownloadFileError> {
|
||||
return Signal { subscriber in
|
||||
let completed = Atomic<Bool>(value: false)
|
||||
let downloadTask = URLSession.shared.downloadTask(with: url, completionHandler: { location, _, error in
|
||||
|
|
@ -761,6 +805,6 @@ struct MergedLocalWalletConfiguration: Codable, Equatable {
|
|||
|
||||
private extension MergedLocalWalletConfiguration {
|
||||
static var `default`: MergedLocalWalletConfiguration {
|
||||
return MergedLocalWalletConfiguration(configuration: LocalWalletConfiguration(source: .url("https://test.ton.org/config.json"), blockchainName: "testchain"), resolved: nil)
|
||||
return MergedLocalWalletConfiguration(configuration: LocalWalletConfiguration(source: .url("https://test.ton.org/config.json"), blockchainName: "testnet"), resolved: nil)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
"Wallet.Info.Send" = "Send";
|
||||
"Wallet.Info.RefreshErrorTitle" = "No network";
|
||||
"Wallet.Info.RefreshErrorText" = "Couldn't refresh balance. Please make sure your internet connection is working and try again.";
|
||||
"Wallet.Info.RefreshErrorNetworkText" = "The wallet state can not be retrieved at this time. Please try again later.";
|
||||
"Wallet.Info.RefreshErrorNetworkText" = "Wallet state can not be retrieved at this time. Please try again later.";
|
||||
"Wallet.Info.UnknownTransaction" = "Empty Transaction";
|
||||
"Wallet.Info.TransactionTo" = "to";
|
||||
"Wallet.Info.TransactionFrom" = "from";
|
||||
|
|
@ -54,7 +54,7 @@
|
|||
"Wallet.Send.Balance" = "Balance: %@";
|
||||
"Wallet.Send.AmountText" = "Grams to send";
|
||||
"Wallet.Send.Confirmation" = "Confirmation";
|
||||
"Wallet.Send.ConfirmationText" = "Do you want to send **%1$@** Grams to\n%2$@?\n\nFees: ~%3$@ grams";
|
||||
"Wallet.Send.ConfirmationText" = "Do you want to send **%1$@** Grams to\n\n%2$@?\n\nBlockchain fees: ~%3$@ grams";
|
||||
"Wallet.Send.ConfirmationConfirm" = "Confirm";
|
||||
"Wallet.Send.Send" = "Send";
|
||||
"Wallet.Send.OwnAddressAlertTitle" = "Warning";
|
||||
|
|
@ -62,8 +62,9 @@
|
|||
"Wallet.Send.OwnAddressAlertProceed" = "Proceed";
|
||||
"Wallet.Send.TransactionInProgress" = "Please wait until the current transaction is completed.";
|
||||
"Wallet.Send.SyncInProgress" = "Please wait while the wallet finishes syncing with the TON Blockchain.";
|
||||
"Wallet.Settings.Title" = "Wallet Settings";
|
||||
"Wallet.Settings.Configuration" = "Configuration";
|
||||
"Wallet.Settings.Title" = "Settings";
|
||||
"Wallet.Settings.Configuration" = "Server Settings";
|
||||
"Wallet.Settings.ConfigurationInfo" = "Advanced Settings";
|
||||
"Wallet.Settings.BackupWallet" = "Backup Wallet";
|
||||
"Wallet.Settings.DeleteWallet" = "Delete Wallet";
|
||||
"Wallet.Settings.DeleteWalletInfo" = "This will disconnect the wallet from this app. You will be able to restore your wallet using 24 secret words – or import another wallet.\n\nGram Wallets are located in the decentralized TON Blockchain. If you want a wallet to be deleted, simply transfer all the grams from it and leave it empty.";
|
||||
|
|
@ -119,7 +120,7 @@
|
|||
"Wallet.TransactionInfo.OtherFeeInfo" = "Blockchain validators collect a tiny fee for processing your decentralized transactions.";
|
||||
"Wallet.TransactionInfo.OtherFeeInfoUrl" = "Blockchain validators collect a tiny fee for processing your decentralized transactions. [More info]()";
|
||||
"Wallet.WordCheck.Title" = "Test Time!";
|
||||
"Wallet.WordCheck.Text" = "Let’s check that you wrote them down correctly. Please enter words\n**%1$@**, **%2$@** and **%3$@** below:";
|
||||
"Wallet.WordCheck.Text" = "Let’s check that you wrote them down correctly. Please enter the words\n**%1$@**, **%2$@** and **%3$@**";
|
||||
"Wallet.WordCheck.Continue" = "Continue";
|
||||
"Wallet.WordCheck.IncorrectHeader" = "Incorrect words!";
|
||||
"Wallet.WordCheck.IncorrectText" = "The secret words you have entered do not match the ones in the list.";
|
||||
|
|
@ -149,8 +150,23 @@
|
|||
"Wallet.Send.SendAnyway" = "Send Anyway";
|
||||
"Wallet.Receive.CreateInvoice" = "Create Invoice";
|
||||
"Wallet.Receive.CreateInvoiceInfo" = "You can specify the amount and purpose of the payment to save the sender some time.";
|
||||
"Wallet.Configuration.Title" = "Configuration";
|
||||
"Wallet.Configuration.Title" = "Server Settings";
|
||||
"Wallet.Configuration.Apply" = "Save";
|
||||
"Wallet.Configuration.SourceHeader" = "SOURCE";
|
||||
"Wallet.Configuration.SourceURL" = "URL";
|
||||
"Wallet.Configuration.SourceJSON" = "JSON";
|
||||
"Wallet.Configuration.SourceInfo" = "Using a different configuration allows you to change Lite Server addresses.";
|
||||
"Wallet.Configuration.BlockchainIdHeader" = "BLOCKCHAIN ID";
|
||||
"Wallet.Configuration.BlockchainIdPlaceholder" = "Blockchain ID";
|
||||
"Wallet.Configuration.BlockchainIdInfo" = "This setting is for developers. Change it only if you are working on creating your own TON network.";
|
||||
"Wallet.Configuration.ApplyErrorTitle" = "Error";
|
||||
"Wallet.Configuration.ApplyErrorTextURLInvalid" = "The URL you have entered is invalid. Please try again.";
|
||||
"Wallet.Configuration.ApplyErrorTextURLUnreachable" = "There was an error while downloading configuration from %@\nPlease try again.";
|
||||
"Wallet.Configuration.ApplyErrorTextURLInvalidData" = "This blockchain configuration is invalid. Please try again.";
|
||||
"Wallet.Configuration.ApplyErrorTextJSONInvalidData" = "This blockchain configuration is invalid. Please try again.";
|
||||
"Wallet.Configuration.BlockchainNameChangedTitle" = "Warning";
|
||||
"Wallet.Configuration.BlockchainNameChangedText" = "Are you sure you want to change the blockchain ID? You don't need this unless you're testing your own TON network.\n\nIf you proceed, you will need to reconnect your wallet using 24 secret words.";
|
||||
"Wallet.Configuration.BlockchainNameChangedProceed" = "Proceed";
|
||||
"Wallet.CreateInvoice.Title" = "Create Invoice";
|
||||
"Wallet.Navigation.Close" = "Close";
|
||||
"Wallet.Navigation.Back" = "Back";
|
||||
|
|
|
|||
77
Wallet/example_wallet_env.sh
Executable file
77
Wallet/example_wallet_env.sh
Executable file
|
|
@ -0,0 +1,77 @@
|
|||
#!/bin/bash
|
||||
|
||||
custom_realpath() {
|
||||
OURPWD=$PWD
|
||||
cd "$(dirname "$1")"
|
||||
LINK=$(readlink "$(basename "$1")")
|
||||
while [ "$LINK" ]; do
|
||||
cd "$(dirname "$LINK")"
|
||||
LINK=$(readlink "$(basename "$1")")
|
||||
done
|
||||
REALPATH="$PWD/$(basename "$1")"
|
||||
cd "$OURPWD"
|
||||
echo "$REALPATH"
|
||||
}
|
||||
|
||||
export TELEGRAM_ENV_SET="1"
|
||||
|
||||
export HOCKEYAPP_ID=""
|
||||
export IS_INTERNAL_BUILD="false"
|
||||
export IS_APPSTORE_BUILD="true"
|
||||
export APPSTORE_ID="1"
|
||||
export APP_SPECIFIC_URL_SCHEME=""
|
||||
export API_ID="0"
|
||||
export API_HASH=""
|
||||
|
||||
if [ -z "$DEVELOPMENT_CODE_SIGN_IDENTITY" ]; then
|
||||
export DEVELOPMENT_CODE_SIGN_IDENTITY="iPhone Developer: AAAAA AAAAA (XXXXXXXXXX)"
|
||||
fi
|
||||
if [ -z "$DISTRIBUTION_CODE_SIGN_IDENTITY" ]; then
|
||||
export DISTRIBUTION_CODE_SIGN_IDENTITY="iPhone Distribution: AAAAA AAAAA (XXXXXXXXXX)"
|
||||
fi
|
||||
if [ -z "$DEVELOPMENT_TEAM" ]; then
|
||||
export DEVELOPMENT_TEAM="XXXXXXXXXX"
|
||||
fi
|
||||
|
||||
if [ -z "$WALLET_BUNDLE_ID" ]; then
|
||||
export WALLET_BUNDLE_ID="reverse.dns.notation"
|
||||
fi
|
||||
|
||||
if [ -z "$BUILD_NUMBER" ]; then
|
||||
echo "BUILD_NUMBER is not defined"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export WALLET_ENTITLEMENTS_APP="Wallet.entitlements"
|
||||
if [ -z "$WALLET_DEVELOPMENT_PROVISIONING_PROFILE_APP" ]; then
|
||||
export WALLET_DEVELOPMENT_PROVISIONING_PROFILE_APP="development profile name"
|
||||
fi
|
||||
if [ -z "$WALLET_DISTRIBUTION_PROVISIONING_PROFILE_APP" ]; then
|
||||
export WALLET_DISTRIBUTION_PROVISIONING_PROFILE_APP="distribution profile name"
|
||||
fi
|
||||
|
||||
BASE_DIR="$(custom_realpath .)"
|
||||
BASE_PATH=$(dirname "$(custom_realpath $0)")
|
||||
BUILDBOX_DIR="buildbox"
|
||||
|
||||
if [ -z "$CODESIGNING_SOURCE_DATA_PATH" ]; then
|
||||
echo "CODESIGNING_SOURCE_DATA_PATH is not defined"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$CODESIGNING_SOURCE_DATA_PATH/profiles" ]; then
|
||||
echo "Expected codesigning directory layout:"
|
||||
echo "$CODESIGNING_SOURCE_DATA_PATH/profiles/appstore/*.mobileprovision"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf "$BASE_DIR/$BUILDBOX_DIR/transient-data/teams/$DEVELOPMENT_TEAM/codesigning"
|
||||
mkdir -p "$BASE_DIR/$BUILDBOX_DIR/transient-data/teams/$DEVELOPMENT_TEAM/codesigning"
|
||||
cp -R "$CODESIGNING_SOURCE_DATA_PATH/"* "$BASE_DIR/$BUILDBOX_DIR/transient-data/teams/$DEVELOPMENT_TEAM/codesigning/"
|
||||
|
||||
export CODESIGNING_DATA_PATH="$BUILDBOX_DIR/transient-data/teams/$DEVELOPMENT_TEAM/codesigning"
|
||||
export CODESIGNING_CERTS_VARIANT="distribution"
|
||||
export CODESIGNING_PROFILES_VARIANT="appstore"
|
||||
export PACKAGE_METHOD="appstore"
|
||||
|
||||
$@
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>5.12.1</string>
|
||||
<string>$(PRODUCT_BUNDLE_SHORT_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${BUILD_NUMBER}</string>
|
||||
<key>UIDeviceFamily</key>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>5.12.1</string>
|
||||
<string>$(PRODUCT_BUNDLE_SHORT_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${BUILD_NUMBER}</string>
|
||||
<key>NSExtension</key>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>5.12.1</string>
|
||||
<string>$(PRODUCT_BUNDLE_SHORT_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${BUILD_NUMBER}</string>
|
||||
<key>NSExtension</key>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,157 @@
|
|||
diff --git a/.gitignore b/.gitignore
|
||||
index 78ce658b9a..30c0369ab7 100644
|
||||
--- a/.gitignore
|
||||
+++ b/.gitignore
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
# IntelliJ build
|
||||
/intellij-out/
|
||||
+/.idea/
|
||||
|
||||
# Buck
|
||||
/buck-out
|
||||
diff --git a/.idea/modules.xml b/.idea/modules.xml
|
||||
deleted file mode 100644
|
||||
index 7ff823b554..0000000000
|
||||
--- a/.idea/modules.xml
|
||||
+++ /dev/null
|
||||
@@ -1,16 +0,0 @@
|
||||
-<?xml version="1.0" encoding="UTF-8"?>
|
||||
-<project version="4">
|
||||
- <component name="ProjectModuleManager">
|
||||
- <modules>
|
||||
- <module fileurl="file://$PROJECT_DIR$/buck.iml" filepath="$PROJECT_DIR$/buck.iml" />
|
||||
- <module fileurl="file://$PROJECT_DIR$/tools/consistency_checker/consistency_checker.iml" filepath="$PROJECT_DIR$/tools/consistency_checker/consistency_checker.iml" />
|
||||
- <module fileurl="file://$PROJECT_DIR$/tools/documentation_generator/documentation_generator.iml" filepath="$PROJECT_DIR$/tools/documentation_generator/documentation_generator.iml" />
|
||||
- <module fileurl="file://$PROJECT_DIR$/tools/build/modules/modules.iml" filepath="$PROJECT_DIR$/tools/build/modules/modules.iml" />
|
||||
- <module fileurl="file://$PROJECT_DIR$/src/com/facebook/buck/multitenant/multitenant.iml" filepath="$PROJECT_DIR$/src/com/facebook/buck/multitenant/multitenant.iml" />
|
||||
- <module fileurl="file://$PROJECT_DIR$/programs/programs.iml" filepath="$PROJECT_DIR$/programs/programs.iml" />
|
||||
- <module fileurl="file://$PROJECT_DIR$/skunkworks/protobuck/protobuck.iml" filepath="$PROJECT_DIR$/skunkworks/protobuck/protobuck.iml" />
|
||||
- <module fileurl="file://$PROJECT_DIR$/python-dsl/python-dsl.iml" filepath="$PROJECT_DIR$/python-dsl/python-dsl.iml" />
|
||||
- <module fileurl="file://$PROJECT_DIR$/scripts/scripts.iml" filepath="$PROJECT_DIR$/scripts/scripts.iml" />
|
||||
- </modules>
|
||||
- </component>
|
||||
-</project>
|
||||
\ No newline at end of file
|
||||
diff --git a/src/com/facebook/buck/apple/AppleBundle.java b/src/com/facebook/buck/apple/AppleBundle.java
|
||||
index d895ab9a79..ad42beb302 100644
|
||||
--- a/src/com/facebook/buck/apple/AppleBundle.java
|
||||
+++ b/src/com/facebook/buck/apple/AppleBundle.java
|
||||
@@ -992,7 +992,11 @@ public class AppleBundle extends AbstractBuildRuleWithDeclaredAndExtraDeps
|
||||
keys.put("DTPlatformName", new NSString(platform.getName()));
|
||||
keys.put("DTPlatformVersion", new NSString(sdkVersion));
|
||||
keys.put("DTSDKName", new NSString(sdkName + sdkVersion));
|
||||
- keys.put("MinimumOSVersion", new NSString(minOSVersion));
|
||||
+ if (infoPlistSubstitutions.containsKey("MinimumOSVersion")) {
|
||||
+ keys.put("MinimumOSVersion", new NSString(infoPlistSubstitutions.get("MinimumOSVersion")));
|
||||
+ } else {
|
||||
+ keys.put("MinimumOSVersion", new NSString(minOSVersion));
|
||||
+ }
|
||||
if (platformBuildVersion.isPresent()) {
|
||||
keys.put("DTPlatformBuild", new NSString(platformBuildVersion.get()));
|
||||
keys.put("DTSDKBuild", new NSString(platformBuildVersion.get()));
|
||||
@@ -1185,9 +1189,10 @@ public class AppleBundle extends AbstractBuildRuleWithDeclaredAndExtraDeps
|
||||
|
||||
// .framework bundles will be code-signed when they're copied into the containing bundle.
|
||||
private boolean needCodeSign() {
|
||||
- return binary.isPresent()
|
||||
+ return false;
|
||||
+ /*return binary.isPresent()
|
||||
&& ApplePlatform.needsCodeSign(platform.getName())
|
||||
- && !extension.equals(FRAMEWORK_EXTENSION);
|
||||
+ && !extension.equals(FRAMEWORK_EXTENSION);*/
|
||||
}
|
||||
|
||||
@Override
|
||||
diff --git a/src/com/facebook/buck/apple/MultiarchFileInfos.java b/src/com/facebook/buck/apple/MultiarchFileInfos.java
|
||||
index c078b2e134..030f9fc289 100644
|
||||
--- a/src/com/facebook/buck/apple/MultiarchFileInfos.java
|
||||
+++ b/src/com/facebook/buck/apple/MultiarchFileInfos.java
|
||||
@@ -177,7 +177,12 @@ public class MultiarchFileInfos {
|
||||
cxxBuckConfig.shouldCacheLinks(),
|
||||
BuildTargetPaths.getGenPath(
|
||||
projectFilesystem, buildTarget, multiarchOutputPathFormat));
|
||||
- graphBuilder.addToIndex(multiarchFile);
|
||||
+ Optional<BuildRule> existingRule2 = graphBuilder.getRuleOptional(multiarchFile.getBuildTarget());
|
||||
+ if (existingRule2.isPresent()) {
|
||||
+ return existingRule2.get();
|
||||
+ } else {
|
||||
+ graphBuilder.addToIndex(multiarchFile);
|
||||
+ }
|
||||
return multiarchFile;
|
||||
} else {
|
||||
return new NoopBuildRule(buildTarget, projectFilesystem);
|
||||
diff --git a/src/com/facebook/buck/features/apple/project/ProjectGenerator.java b/src/com/facebook/buck/features/apple/project/ProjectGenerator.java
|
||||
index 8db968b982..b10f793d8e 100644
|
||||
--- a/src/com/facebook/buck/features/apple/project/ProjectGenerator.java
|
||||
+++ b/src/com/facebook/buck/features/apple/project/ProjectGenerator.java
|
||||
@@ -825,6 +825,7 @@ public class ProjectGenerator {
|
||||
Optional.of(xcodeDescriptions.getXCodeDescriptions()));
|
||||
if (bundleRequiresRemovalOfAllTransitiveFrameworks(targetNode)) {
|
||||
copiedRules = rulesWithoutFrameworkBundles(copiedRules);
|
||||
+ copiedRules = rulesWithoutDylibs(copiedRules);
|
||||
} else if (bundleRequiresAllTransitiveFrameworks(binaryNode, bundleLoaderNode)) {
|
||||
copiedRules =
|
||||
ImmutableSet.<TargetNode<?>>builder()
|
||||
@@ -954,6 +955,22 @@ public class ProjectGenerator {
|
||||
.toImmutableList();
|
||||
}
|
||||
|
||||
+ private ImmutableList<TargetNode<?>> rulesWithoutDylibs(
|
||||
+ Iterable<TargetNode<?>> copiedRules) {
|
||||
+ return RichStream.from(copiedRules)
|
||||
+ .filter(
|
||||
+ input ->
|
||||
+ TargetNodes.castArg(input, AppleLibraryDescriptionArg.class)
|
||||
+ .map(argTargetNode -> {
|
||||
+ if (argTargetNode.getBuildTarget().getFlavors().contains(CxxDescriptionEnhancer.SHARED_FLAVOR)) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ return true;
|
||||
+ })
|
||||
+ .orElse(true))
|
||||
+ .toImmutableList();
|
||||
+ }
|
||||
+
|
||||
private ImmutableList<TargetNode<?>> rulesWithoutBundleLoader(
|
||||
Iterable<TargetNode<?>> copiedRules, TargetNode<?> bundleLoader) {
|
||||
return RichStream.from(copiedRules).filter(x -> !bundleLoader.equals(x)).toImmutableList();
|
||||
@@ -2316,8 +2333,9 @@ public class ProjectGenerator {
|
||||
.transform(
|
||||
bundleExtension -> {
|
||||
switch (bundleExtension) {
|
||||
- case APP:
|
||||
case APPEX:
|
||||
+ return false;
|
||||
+ case APP:
|
||||
case PLUGIN:
|
||||
case BUNDLE:
|
||||
case XCTEST:
|
||||
@@ -2515,7 +2533,7 @@ public class ProjectGenerator {
|
||||
|
||||
librarySearchPaths.add("$DT_TOOLCHAIN_DIR/usr/lib/swift/$PLATFORM_NAME");
|
||||
if (options.shouldLinkSystemSwift()) {
|
||||
- librarySearchPaths.add("$DT_TOOLCHAIN_DIR/usr/lib/swift-5.0/$PLATFORM_NAME");
|
||||
+ //librarySearchPaths.add("$DT_TOOLCHAIN_DIR/usr/lib/swift-5.0/$PLATFORM_NAME");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3444,7 +3462,7 @@ public class ProjectGenerator {
|
||||
|
||||
PBXFileReference fileReference = getLibraryFileReference(targetNode);
|
||||
PBXBuildFile buildFile = new PBXBuildFile(fileReference);
|
||||
- if (fileReference.getExplicitFileType().equals(Optional.of("wrapper.framework"))) {
|
||||
+ if (fileReference.getExplicitFileType().equals(Optional.of("wrapper.framework")) || fileReference.getExplicitFileType().equals(Optional.of("compiled.mach-o.dylib"))) {
|
||||
UnflavoredBuildTargetView buildTarget =
|
||||
targetNode.getBuildTarget().getUnflavoredBuildTarget();
|
||||
if (frameworkTargets.contains(buildTarget)) {
|
||||
@@ -4696,6 +4714,9 @@ public class ProjectGenerator {
|
||||
|
||||
private static boolean bundleRequiresRemovalOfAllTransitiveFrameworks(
|
||||
TargetNode<? extends HasAppleBundleFields> targetNode) {
|
||||
+ if (targetNode.getConstructorArg().getXcodeProductType().equals(Optional.of("com.apple.product-type.app-extension"))) {
|
||||
+ return true;
|
||||
+ }
|
||||
return isFrameworkBundle(targetNode.getConstructorArg());
|
||||
}
|
||||
|
||||
7
buildbox/buck/prepare_buck_source.sh
Normal file
7
buildbox/buck/prepare_buck_source.sh
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
|
||||
ls []
|
||||
|
||||
if [ ! -f "" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -22,12 +22,30 @@ def clean_files(base_dir, dirs, files):
|
|||
if re.match('.*\\.xcodeproj', dir) or re.match('.*\\.xcworkspace', dir):
|
||||
shutil.rmtree(base_dir + '/' + dir, ignore_errors=True)
|
||||
|
||||
def clean_dep_files(base_dir, dirs, files):
|
||||
for file in files:
|
||||
if re.match('^\\.git$', file) or re.match('^.*/\\.git$', file):
|
||||
os.remove(base_dir + '/' + file)
|
||||
for dir in dirs:
|
||||
if re.match('^\\.git$', dir) or re.match('^.*/\\.git$', dir):
|
||||
shutil.rmtree(base_dir + '/' + dir, ignore_errors=True)
|
||||
|
||||
if len(sys.argv) != 2:
|
||||
print('Usage: extract_wallet_source.py destination')
|
||||
sys.exit(1)
|
||||
|
||||
destination = sys.argv[1]
|
||||
|
||||
initial_dirs, initial_files = get_file_list(destination)
|
||||
for file in initial_files:
|
||||
if re.match('^\\.git/.*$', file):
|
||||
continue
|
||||
os.remove(destination + '/' + file)
|
||||
for dir in initial_dirs:
|
||||
if dir == '.git' or re.match('^\\.git/.*$', dir):
|
||||
continue
|
||||
shutil.rmtree(destination + '/' + dir, ignore_errors=True)
|
||||
|
||||
deps_data = os.popen('make -f Wallet.makefile --silent wallet_deps').read()
|
||||
|
||||
deps = json.loads(deps_data)
|
||||
|
|
@ -45,6 +63,8 @@ for dep in deps:
|
|||
|
||||
for dep_path in paths:
|
||||
shutil.copytree(dep_path, destination + '/' + dep_path)
|
||||
dep_dirs, dep_files = get_file_list(destination + '/' + dep_path)
|
||||
clean_dep_files(destination + '/' + dep_path, dep_dirs, dep_files)
|
||||
|
||||
result_dirs, result_files = get_file_list(destination)
|
||||
clean_files(destination, result_dirs, result_files)
|
||||
|
|
@ -53,6 +73,10 @@ with open(destination + '/BUCK', 'w+b') as file:
|
|||
pass
|
||||
|
||||
shutil.copytree('Config', destination + '/' + 'Config')
|
||||
shutil.copytree('tools/buck-build', destination + '/' + 'tools/buck-build')
|
||||
|
||||
shutil.copy('Wallet/README.md', destination + '/' + 'README.md')
|
||||
os.remove(destination + '/Wallet/' + 'README.md')
|
||||
|
||||
copy_files = [
|
||||
'.buckconfig',
|
||||
|
|
|
|||
|
|
@ -17,12 +17,12 @@ private func isLocked(passcodeSettings: PresentationPasscodeSettings, state: Loc
|
|||
} else if let autolockTimeout = passcodeSettings.autolockTimeout {
|
||||
var bootTimestamp: Int32 = 0
|
||||
let uptime = getDeviceUptimeSeconds(&bootTimestamp)
|
||||
let timestamp = MonotonicTimestamp(bootTimestap: bootTimestamp, uptime: uptime)
|
||||
let timestamp = MonotonicTimestamp(bootTimestamp: bootTimestamp, uptime: uptime)
|
||||
|
||||
let applicationActivityTimestamp = state.applicationActivityTimestamp
|
||||
|
||||
if let applicationActivityTimestamp = applicationActivityTimestamp {
|
||||
if timestamp.bootTimestap != applicationActivityTimestamp.bootTimestap {
|
||||
if timestamp.bootTimestamp != applicationActivityTimestamp.bootTimestamp {
|
||||
return true
|
||||
}
|
||||
if timestamp.uptime >= applicationActivityTimestamp.uptime + autolockTimeout {
|
||||
|
|
@ -249,7 +249,7 @@ public final class AppLockContextImpl: AppLockContext {
|
|||
let uptime = getDeviceUptimeSeconds(&bootTimestamp)
|
||||
|
||||
var state = state
|
||||
state.applicationActivityTimestamp = MonotonicTimestamp(bootTimestap: bootTimestamp, uptime: uptime)
|
||||
state.applicationActivityTimestamp = MonotonicTimestamp(bootTimestamp: bootTimestamp, uptime: uptime)
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
|
@ -276,7 +276,7 @@ public final class AppLockContextImpl: AppLockContext {
|
|||
return self.currentState.get()
|
||||
|> map { state in
|
||||
return state.unlockAttemts.flatMap { unlockAttemts in
|
||||
return AccessChallengeAttempts(count: unlockAttemts.count, timestamp: unlockAttemts.wallClockTimestamp)
|
||||
return AccessChallengeAttempts(count: unlockAttemts.count, bootTimestamp: unlockAttemts.timestamp.bootTimestamp, uptime: unlockAttemts.timestamp.uptime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -299,7 +299,7 @@ public final class AppLockContextImpl: AppLockContext {
|
|||
|
||||
var bootTimestamp: Int32 = 0
|
||||
let uptime = getDeviceUptimeSeconds(&bootTimestamp)
|
||||
let timestamp = MonotonicTimestamp(bootTimestap: bootTimestamp, uptime: uptime)
|
||||
let timestamp = MonotonicTimestamp(bootTimestamp: bootTimestamp, uptime: uptime)
|
||||
state.applicationActivityTimestamp = timestamp
|
||||
|
||||
return state
|
||||
|
|
@ -309,9 +309,15 @@ public final class AppLockContextImpl: AppLockContext {
|
|||
public func failedUnlockAttempt() {
|
||||
self.updateLockState { state in
|
||||
var state = state
|
||||
var unlockAttemts = state.unlockAttemts ?? UnlockAttempts(count: 0, wallClockTimestamp: 0)
|
||||
var unlockAttemts = state.unlockAttemts ?? UnlockAttempts(count: 0, timestamp: MonotonicTimestamp(bootTimestamp: 0, uptime: 0))
|
||||
|
||||
unlockAttemts.count += 1
|
||||
unlockAttemts.wallClockTimestamp = Int32(CFAbsoluteTimeGetCurrent())
|
||||
|
||||
var bootTimestamp: Int32 = 0
|
||||
let uptime = getDeviceUptimeSeconds(&bootTimestamp)
|
||||
let timestamp = MonotonicTimestamp(bootTimestamp: bootTimestamp, uptime: uptime)
|
||||
|
||||
unlockAttemts.timestamp = timestamp
|
||||
state.unlockAttemts = unlockAttemts
|
||||
return state
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,22 +2,22 @@ import Foundation
|
|||
import MonotonicTime
|
||||
|
||||
public struct MonotonicTimestamp: Codable, Equatable {
|
||||
public var bootTimestap: Int32
|
||||
public var bootTimestamp: Int32
|
||||
public var uptime: Int32
|
||||
|
||||
public init(bootTimestap: Int32, uptime: Int32) {
|
||||
self.bootTimestap = bootTimestap
|
||||
public init(bootTimestamp: Int32, uptime: Int32) {
|
||||
self.bootTimestamp = bootTimestamp
|
||||
self.uptime = uptime
|
||||
}
|
||||
}
|
||||
|
||||
public struct UnlockAttempts: Codable, Equatable {
|
||||
public var count: Int32
|
||||
public var wallClockTimestamp: Int32
|
||||
public var timestamp: MonotonicTimestamp
|
||||
|
||||
public init(count: Int32, wallClockTimestamp: Int32) {
|
||||
public init(count: Int32, timestamp: MonotonicTimestamp) {
|
||||
self.count = count
|
||||
self.wallClockTimestamp = wallClockTimestamp
|
||||
self.timestamp = timestamp
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -45,10 +45,10 @@ public func isAppLocked(state: LockState) -> Bool {
|
|||
} else if let autolockTimeout = state.autolockTimeout {
|
||||
var bootTimestamp: Int32 = 0
|
||||
let uptime = getDeviceUptimeSeconds(&bootTimestamp)
|
||||
let timestamp = MonotonicTimestamp(bootTimestap: bootTimestamp, uptime: uptime)
|
||||
let timestamp = MonotonicTimestamp(bootTimestamp: bootTimestamp, uptime: uptime)
|
||||
|
||||
if let applicationActivityTimestamp = state.applicationActivityTimestamp {
|
||||
if timestamp.bootTimestap != applicationActivityTimestamp.bootTimestap {
|
||||
if timestamp.bootTimestamp != applicationActivityTimestamp.bootTimestamp {
|
||||
return true
|
||||
}
|
||||
if timestamp.uptime >= applicationActivityTimestamp.uptime + autolockTimeout {
|
||||
|
|
|
|||
22
submodules/AsyncDisplayKit/.buckconfig
vendored
22
submodules/AsyncDisplayKit/.buckconfig
vendored
|
|
@ -1,22 +0,0 @@
|
|||
[cxx]
|
||||
default_platform = iphonesimulator-x86_64
|
||||
combined_preprocess_and_compile = true
|
||||
|
||||
[apple]
|
||||
iphonesimulator_target_sdk_version = 8.0
|
||||
iphoneos_target_sdk_version = 8.0
|
||||
xctool_default_destination_specifier = platform=iOS Simulator, name=iPhone 6, OS=10.2
|
||||
|
||||
[alias]
|
||||
lib = //:AsyncDisplayKit
|
||||
tests = //:Tests
|
||||
|
||||
[httpserver]
|
||||
port = 8080
|
||||
|
||||
[project]
|
||||
ide = xcode
|
||||
ignore = .buckd, \
|
||||
.hg, \
|
||||
.git, \
|
||||
buck-out, \
|
||||
1
submodules/AsyncDisplayKit/.buckversion
vendored
1
submodules/AsyncDisplayKit/.buckversion
vendored
|
|
@ -1 +0,0 @@
|
|||
f399f484bf13b47bcc2bf0f2e092ab5d8de9f6e6
|
||||
19
submodules/AsyncDisplayKit/.editorconfig
vendored
19
submodules/AsyncDisplayKit/.editorconfig
vendored
|
|
@ -1,19 +0,0 @@
|
|||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[**.{h,cc,mm,m}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.{md,markdown}]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# Makefiles always use tabs for indentation
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
// If you're looking for help, please consider joining our slack channel:
|
||||
// http://asyncdisplaykit.org/slack (we'll be updating the name to Texture soon)
|
||||
|
||||
// The more information you include, the faster we can help you out!
|
||||
// Please include: a sample project or screenshots, code snippets
|
||||
// Texture version, and/or backtraces for any crashes (> bt all).
|
||||
// Please delete these lines before posting. Thanks!
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
issues=false
|
||||
since-tag=2.8
|
||||
future-release=2.9
|
||||
5
submodules/AsyncDisplayKit/.slather.yml
vendored
5
submodules/AsyncDisplayKit/.slather.yml
vendored
|
|
@ -1,5 +0,0 @@
|
|||
ci_service: travis_ci
|
||||
coverage_service: coveralls
|
||||
xcodeproj: AsyncDisplayKit.xcodeproj
|
||||
source_directory: AsyncDisplayKit
|
||||
|
||||
34
submodules/AsyncDisplayKit/.travis.yml
vendored
34
submodules/AsyncDisplayKit/.travis.yml
vendored
|
|
@ -1,34 +0,0 @@
|
|||
language: objective-c
|
||||
cache:
|
||||
- bundler
|
||||
- cocoapods
|
||||
osx_image: xcode8.1
|
||||
git:
|
||||
depth: 10
|
||||
before_install:
|
||||
- brew update
|
||||
- brew outdated xctool || brew upgrade xctool
|
||||
- brew outdated carthage || brew upgrade carthage
|
||||
- gem install cocoapods -v 1.0.1
|
||||
- gem install xcpretty -v 0.2.2
|
||||
- gem install xcpretty-travis-formatter
|
||||
# - gem install slather
|
||||
- xcrun simctl list
|
||||
install: echo "<3"
|
||||
env:
|
||||
- MODE=tests
|
||||
- MODE=tests_listkit
|
||||
- MODE=examples-pt1
|
||||
- MODE=examples-pt2
|
||||
- MODE=examples-pt3
|
||||
- MODE=life-without-cocoapods
|
||||
- MODE=framework
|
||||
script: ./build.sh $MODE
|
||||
|
||||
#after_success:
|
||||
# - slather
|
||||
|
||||
# whitelist
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
./build.sh all
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
[
|
||||
"^plans/",
|
||||
"^docs/",
|
||||
"^CI/exclude-from-build.json$",
|
||||
"^**/*.md$"
|
||||
]
|
||||
|
|
@ -71,6 +71,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
//! @abstract The range of text currently selected. If length is zero, the range is the cursor location.
|
||||
@property NSRange selectedRange;
|
||||
|
||||
@property (readonly) CGRect selectionRect;
|
||||
|
||||
#pragma mark - Placeholder
|
||||
/**
|
||||
@abstract Indicates if the receiver is displaying the placeholder text.
|
||||
|
|
|
|||
|
|
@ -566,6 +566,15 @@
|
|||
_textKitComponents.textView.selectedRange = selectedRange;
|
||||
}
|
||||
|
||||
- (CGRect)selectionRect {
|
||||
UITextRange *range = [_textKitComponents.textView selectedTextRange];
|
||||
if (range != nil) {
|
||||
return [_textKitComponents.textView firstRectForRange:range];
|
||||
} else {
|
||||
return [_textKitComponents.textView bounds];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Placeholder
|
||||
- (BOOL)isDisplayingPlaceholder
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,70 +0,0 @@
|
|||
//
|
||||
// ASAbsoluteLayoutSpecSnapshotTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutSpecSnapshotTestsHelper.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASAbsoluteLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASBackgroundLayoutSpec.h>
|
||||
|
||||
@interface ASAbsoluteLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase
|
||||
@end
|
||||
|
||||
@implementation ASAbsoluteLayoutSpecSnapshotTests
|
||||
|
||||
- (void)testSizingBehaviour
|
||||
{
|
||||
[self testWithSizeRange:ASSizeRangeMake(CGSizeMake(150, 200), CGSizeMake(INFINITY, INFINITY))
|
||||
identifier:@"underflowChildren"];
|
||||
[self testWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeMake(50, 100))
|
||||
identifier:@"overflowChildren"];
|
||||
// Expect the spec to wrap its content because children sizes are between constrained size
|
||||
[self testWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY / 2, INFINITY / 2))
|
||||
identifier:@"wrappedChildren"];
|
||||
}
|
||||
|
||||
- (void)testChildrenMeasuredWithAutoMaxSize
|
||||
{
|
||||
ASDisplayNode *firstChild = ASDisplayNodeWithBackgroundColor([UIColor redColor], (CGSize){50, 50});
|
||||
firstChild.style.layoutPosition = CGPointMake(0, 0);
|
||||
|
||||
ASDisplayNode *secondChild = ASDisplayNodeWithBackgroundColor([UIColor blueColor], (CGSize){100, 100});
|
||||
secondChild.style.layoutPosition = CGPointMake(10, 60);
|
||||
|
||||
ASSizeRange sizeRange = ASSizeRangeMake(CGSizeMake(10, 10), CGSizeMake(110, 160));
|
||||
[self testWithChildren:@[firstChild, secondChild] sizeRange:sizeRange identifier:nil];
|
||||
}
|
||||
|
||||
- (void)testWithSizeRange:(ASSizeRange)sizeRange identifier:(NSString *)identifier
|
||||
{
|
||||
ASDisplayNode *firstChild = ASDisplayNodeWithBackgroundColor([UIColor redColor], (CGSize){50, 50});
|
||||
firstChild.style.layoutPosition = CGPointMake(0, 0);
|
||||
|
||||
ASDisplayNode *secondChild = ASDisplayNodeWithBackgroundColor([UIColor blueColor], (CGSize){100, 100});
|
||||
secondChild.style.layoutPosition = CGPointMake(0, 50);
|
||||
|
||||
[self testWithChildren:@[firstChild, secondChild] sizeRange:sizeRange identifier:identifier];
|
||||
}
|
||||
|
||||
- (void)testWithChildren:(NSArray *)children sizeRange:(ASSizeRange)sizeRange identifier:(NSString *)identifier
|
||||
{
|
||||
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor whiteColor]);
|
||||
|
||||
NSMutableArray *subnodes = [NSMutableArray arrayWithArray:children];
|
||||
[subnodes insertObject:backgroundNode atIndex:0];
|
||||
|
||||
ASLayoutSpec *layoutSpec =
|
||||
[ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:
|
||||
[ASAbsoluteLayoutSpec
|
||||
absoluteLayoutSpecWithChildren:children]
|
||||
background:backgroundNode];
|
||||
|
||||
[self testLayoutSpec:layoutSpec sizeRange:sizeRange subnodes:subnodes identifier:identifier];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
//
|
||||
// ASBackgroundLayoutSpecSnapshotTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutSpecSnapshotTestsHelper.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASBackgroundLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASCenterLayoutSpec.h>
|
||||
|
||||
static const ASSizeRange kSize = {{320, 320}, {320, 320}};
|
||||
|
||||
@interface ASBackgroundLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASBackgroundLayoutSpecSnapshotTests
|
||||
|
||||
- (void)testBackground
|
||||
{
|
||||
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor blueColor]);
|
||||
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor blackColor], {20, 20});
|
||||
|
||||
ASLayoutSpec *layoutSpec =
|
||||
[ASBackgroundLayoutSpec
|
||||
backgroundLayoutSpecWithChild:
|
||||
[ASCenterLayoutSpec
|
||||
centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY
|
||||
sizingOptions:{}
|
||||
child:foregroundNode]
|
||||
background:backgroundNode];
|
||||
|
||||
[self testLayoutSpec:layoutSpec sizeRange:kSize subnodes:@[backgroundNode, foregroundNode] identifier: nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
//
|
||||
// ASBasicImageDownloaderContextTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASBasicImageDownloader.h>
|
||||
#import <AsyncDisplayKit/ASBasicImageDownloaderInternal.h>
|
||||
|
||||
#import <OCMock/OCMock.h>
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
|
||||
@interface ASBasicImageDownloaderContextTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASBasicImageDownloaderContextTests
|
||||
|
||||
- (NSURL *)randomURL
|
||||
{
|
||||
// random URL for each test, doesn't matter that this is not really a URL
|
||||
return [NSURL URLWithString:[NSUUID UUID].UUIDString];
|
||||
}
|
||||
|
||||
- (void)testContextCreation
|
||||
{
|
||||
NSURL *url = [self randomURL];
|
||||
ASBasicImageDownloaderContext *c1 = [ASBasicImageDownloaderContext contextForURL:url];
|
||||
ASBasicImageDownloaderContext *c2 = [ASBasicImageDownloaderContext contextForURL:url];
|
||||
XCTAssert(c1 == c2, @"Context objects are not the same");
|
||||
}
|
||||
|
||||
- (void)testContextInvalidation
|
||||
{
|
||||
NSURL *url = [self randomURL];
|
||||
ASBasicImageDownloaderContext *context = [ASBasicImageDownloaderContext contextForURL:url];
|
||||
[context cancel];
|
||||
XCTAssert([context isCancelled], @"Context should be cancelled");
|
||||
}
|
||||
|
||||
/* This test is currently unreliable. See https://github.com/facebook/AsyncDisplayKit/issues/459
|
||||
- (void)testAsyncContextInvalidation
|
||||
{
|
||||
NSURL *url = [self randomURL];
|
||||
ASBasicImageDownloaderContext *context = [ASBasicImageDownloaderContext contextForURL:url];
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Context invalidation"];
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[expectation fulfill];
|
||||
XCTAssert([context isCancelled], @"Context should be cancelled");
|
||||
});
|
||||
|
||||
[context cancel];
|
||||
[self waitForExpectationsWithTimeout:30.0 handler:nil];
|
||||
}
|
||||
*/
|
||||
|
||||
- (void)testContextSessionCanceled
|
||||
{
|
||||
NSURL *url = [self randomURL];
|
||||
id task = [OCMockObject mockForClass:[NSURLSessionTask class]];
|
||||
ASBasicImageDownloaderContext *context = [ASBasicImageDownloaderContext contextForURL:url];
|
||||
context.sessionTask = task;
|
||||
|
||||
[[task expect] cancel];
|
||||
|
||||
[context cancel];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
//
|
||||
// ASBasicImageDownloaderTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASBasicImageDownloader.h>
|
||||
|
||||
@interface ASBasicImageDownloaderTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASBasicImageDownloaderTests
|
||||
|
||||
- (void)testAsynchronouslyDownloadTheSameURLTwice
|
||||
{
|
||||
XCTestExpectation *firstExpectation = [self expectationWithDescription:@"First ASBasicImageDownloader completion handler should be called within 3 seconds"];
|
||||
XCTestExpectation *secondExpectation = [self expectationWithDescription:@"Second ASBasicImageDownloader completion handler should be called within 3 seconds"];
|
||||
|
||||
ASBasicImageDownloader *downloader = [ASBasicImageDownloader sharedImageDownloader];
|
||||
NSURL *URL = [NSURL URLWithString:@"http://wrongPath/wrongResource.png"];
|
||||
|
||||
[downloader downloadImageWithURL:URL
|
||||
callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
|
||||
downloadProgress:nil
|
||||
completion:^(id<ASImageContainerProtocol> _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo) {
|
||||
[firstExpectation fulfill];
|
||||
}];
|
||||
|
||||
[downloader downloadImageWithURL:URL
|
||||
callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
|
||||
downloadProgress:nil
|
||||
completion:^(id<ASImageContainerProtocol> _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo) {
|
||||
[secondExpectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectationsWithTimeout:30 handler:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
//
|
||||
// ASBatchFetchingTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <AsyncDisplayKit/ASBatchFetching.h>
|
||||
|
||||
@interface ASBatchFetchingTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASBatchFetchingTests
|
||||
|
||||
#define PASSING_RECT CGRectMake(0,0,1,1)
|
||||
#define PASSING_SIZE CGSizeMake(1,1)
|
||||
#define PASSING_POINT CGPointMake(1,1)
|
||||
#define VERTICAL_RECT(h) CGRectMake(0,0,1,h)
|
||||
#define VERTICAL_SIZE(h) CGSizeMake(0,h)
|
||||
#define VERTICAL_OFFSET(y) CGPointMake(0,y)
|
||||
#define HORIZONTAL_RECT(w) CGRectMake(0,0,w,1)
|
||||
#define HORIZONTAL_SIZE(w) CGSizeMake(w,0)
|
||||
#define HORIZONTAL_OFFSET(x) CGPointMake(x,0)
|
||||
|
||||
- (void)testBatchNullState {
|
||||
ASBatchContext *context = [[ASBatchContext alloc] init];
|
||||
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, CGRectZero, CGSizeZero, CGPointZero, 0.0, YES, CGPointZero, nil);
|
||||
XCTAssert(shouldFetch == NO, @"Should not fetch in the null state");
|
||||
}
|
||||
|
||||
- (void)testBatchAlreadyFetching {
|
||||
ASBatchContext *context = [[ASBatchContext alloc] init];
|
||||
[context beginBatchFetching];
|
||||
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(shouldFetch == NO, @"Should not fetch when context is already fetching");
|
||||
}
|
||||
|
||||
- (void)testUnsupportedScrollDirections {
|
||||
ASBatchContext *context = [[ASBatchContext alloc] init];
|
||||
BOOL fetchRight = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, ASScrollDirectionHorizontalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(fetchRight == YES, @"Should fetch for scrolling right");
|
||||
BOOL fetchDown = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(fetchDown == YES, @"Should fetch for scrolling down");
|
||||
BOOL fetchUp = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, ASScrollDirectionVerticalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(fetchUp == NO, @"Should not fetch for scrolling up");
|
||||
BOOL fetchLeft = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionLeft, ASScrollDirectionHorizontalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(fetchLeft == NO, @"Should not fetch for scrolling left");
|
||||
}
|
||||
|
||||
- (void)testVerticalScrollToExactLeading {
|
||||
CGFloat screen = 1.0;
|
||||
ASBatchContext *context = [[ASBatchContext alloc] init];
|
||||
// scroll to 1-screen top offset, height is 1 screen, so bottom is 1 screen away from end of content
|
||||
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 1.0), 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(shouldFetch == YES, @"Fetch should begin when vertically scrolling to exactly 1 leading screen away");
|
||||
}
|
||||
|
||||
- (void)testVerticalScrollToLessThanLeading {
|
||||
CGFloat screen = 1.0;
|
||||
ASBatchContext *context = [[ASBatchContext alloc] init];
|
||||
// 3 screens of content, scroll only 1/2 of one screen
|
||||
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 0.5), 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(shouldFetch == NO, @"Fetch should not begin when vertically scrolling less than the leading distance away");
|
||||
}
|
||||
|
||||
- (void)testVerticalScrollingPastContentSize {
|
||||
CGFloat screen = 1.0;
|
||||
ASBatchContext *context = [[ASBatchContext alloc] init];
|
||||
// 3 screens of content, top offset to 3-screens, height 1 screen, so its 1 screen past the leading
|
||||
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 3.0), 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(shouldFetch == YES, @"Fetch should begin when vertically scrolling past the content size");
|
||||
}
|
||||
|
||||
- (void)testHorizontalScrollToExactLeading {
|
||||
CGFloat screen = 1.0;
|
||||
ASBatchContext *context = [[ASBatchContext alloc] init];
|
||||
// scroll to 1-screen left offset, width is 1 screen, so right is 1 screen away from end of content
|
||||
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, ASScrollDirectionVerticalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 1.0), 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(shouldFetch == YES, @"Fetch should begin when horizontally scrolling to exactly 1 leading screen away");
|
||||
}
|
||||
|
||||
- (void)testHorizontalScrollToLessThanLeading {
|
||||
CGFloat screen = 1.0;
|
||||
ASBatchContext *context = [[ASBatchContext alloc] init];
|
||||
// 3 screens of content, scroll only 1/2 of one screen
|
||||
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionLeft, ASScrollDirectionHorizontalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 0.5), 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(shouldFetch == NO, @"Fetch should not begin when horizontally scrolling less than the leading distance away");
|
||||
}
|
||||
|
||||
- (void)testHorizontalScrollingPastContentSize {
|
||||
CGFloat screen = 1.0;
|
||||
ASBatchContext *context = [[ASBatchContext alloc] init];
|
||||
// 3 screens of content, left offset to 3-screens, width 1 screen, so its 1 screen past the leading
|
||||
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionHorizontalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 3.0), 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(shouldFetch == YES, @"Fetch should begin when vertically scrolling past the content size");
|
||||
}
|
||||
|
||||
- (void)testVerticalScrollingSmallContentSize {
|
||||
CGFloat screen = 1.0;
|
||||
ASBatchContext *context = [[ASBatchContext alloc] init];
|
||||
// when the content size is < screen size, the target offset will always be 0
|
||||
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 0.5), VERTICAL_OFFSET(0.0), 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(shouldFetch == YES, @"Fetch should begin when the target is 0 and the content size is smaller than the scree");
|
||||
}
|
||||
|
||||
- (void)testHorizontalScrollingSmallContentSize {
|
||||
CGFloat screen = 1.0;
|
||||
ASBatchContext *context = [[ASBatchContext alloc] init];
|
||||
// when the content size is < screen size, the target offset will always be 0
|
||||
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, ASScrollDirectionHorizontalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 0.5), HORIZONTAL_OFFSET(0.0), 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(shouldFetch == YES, @"Fetch should begin when the target is 0 and the content size is smaller than the scree");
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,231 +0,0 @@
|
|||
//
|
||||
// ASBridgedPropertiesTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/ASPendingStateController.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
|
||||
#import <AsyncDisplayKit/_ASPendingState.h>
|
||||
#import <AsyncDisplayKit/ASCellNode.h>
|
||||
|
||||
@interface ASPendingStateController (Testing)
|
||||
- (BOOL)test_isFlushScheduled;
|
||||
@end
|
||||
|
||||
@interface ASBridgedPropertiesTestView : UIView
|
||||
@property (nonatomic, readonly) BOOL receivedSetNeedsLayout;
|
||||
@end
|
||||
|
||||
@implementation ASBridgedPropertiesTestView
|
||||
|
||||
- (void)setNeedsLayout
|
||||
{
|
||||
_receivedSetNeedsLayout = YES;
|
||||
[super setNeedsLayout];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASBridgedPropertiesTestNode : ASDisplayNode
|
||||
@property (nullable, nonatomic, copy) dispatch_block_t onDealloc;
|
||||
@end
|
||||
|
||||
@implementation ASBridgedPropertiesTestNode
|
||||
|
||||
- (void)dealloc {
|
||||
_onDealloc();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASBridgedPropertiesTests : XCTestCase
|
||||
@end
|
||||
|
||||
/// Dispatches the given block synchronously onto a different thread.
|
||||
/// This is useful for testing non-main-thread behavior because `dispatch_sync`
|
||||
/// will often use the current thread.
|
||||
static inline void ASDispatchSyncOnOtherThread(dispatch_block_t block) {
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||||
dispatch_group_enter(group);
|
||||
dispatch_async(q, ^{
|
||||
ASDisplayNodeCAssertNotMainThread();
|
||||
block();
|
||||
dispatch_group_leave(group);
|
||||
});
|
||||
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
|
||||
}
|
||||
|
||||
@implementation ASBridgedPropertiesTests
|
||||
|
||||
- (void)testTheresASharedInstance
|
||||
{
|
||||
XCTAssertNotNil([ASPendingStateController sharedInstance]);
|
||||
}
|
||||
|
||||
/// FIXME: This test is unreliable for an as-yet unknown reason
|
||||
/// but that being intermittent, and this test being so strict, it's
|
||||
/// reasonable to assume for now the failures don't reflect a framework bug.
|
||||
/// See https://github.com/facebook/AsyncDisplayKit/pull/1048
|
||||
- (void)DISABLED_testThatDirtyNodesAreNotRetained
|
||||
{
|
||||
ASPendingStateController *ctrl = [ASPendingStateController sharedInstance];
|
||||
__block BOOL didDealloc = NO;
|
||||
@autoreleasepool {
|
||||
__attribute__((objc_precise_lifetime)) ASBridgedPropertiesTestNode *node = [ASBridgedPropertiesTestNode new];
|
||||
node.onDealloc = ^{
|
||||
didDealloc = YES;
|
||||
};
|
||||
[node view];
|
||||
XCTAssertEqual(node.alpha, 1);
|
||||
ASDispatchSyncOnOtherThread(^{
|
||||
node.alpha = 0;
|
||||
});
|
||||
XCTAssertEqual(node.alpha, 1);
|
||||
XCTAssert(ctrl.test_isFlushScheduled);
|
||||
}
|
||||
XCTAssertTrue(didDealloc);
|
||||
}
|
||||
|
||||
- (void)testThatSettingABridgedViewPropertyInBackgroundGetsFlushedOnNextRunLoop
|
||||
{
|
||||
ASDisplayNode *node = [ASDisplayNode new];
|
||||
[node view];
|
||||
XCTAssertEqual(node.alpha, 1);
|
||||
ASDispatchSyncOnOtherThread(^{
|
||||
node.alpha = 0;
|
||||
});
|
||||
XCTAssertEqual(node.alpha, 1);
|
||||
[self waitForMainDispatchQueueToFlush];
|
||||
XCTAssertEqual(node.alpha, 0);
|
||||
}
|
||||
|
||||
- (void)testThatSettingABridgedLayerPropertyInBackgroundGetsFlushedOnNextRunLoop
|
||||
{
|
||||
ASDisplayNode *node = [ASDisplayNode new];
|
||||
[node view];
|
||||
XCTAssertEqual(node.shadowOpacity, 0);
|
||||
ASDispatchSyncOnOtherThread(^{
|
||||
node.shadowOpacity = 1;
|
||||
});
|
||||
XCTAssertEqual(node.shadowOpacity, 0);
|
||||
[self waitForMainDispatchQueueToFlush];
|
||||
XCTAssertEqual(node.shadowOpacity, 1);
|
||||
}
|
||||
|
||||
- (void)testThatReadingABridgedViewPropertyInBackgroundThrowsAnException
|
||||
{
|
||||
ASDisplayNode *node = [ASDisplayNode new];
|
||||
[node view];
|
||||
ASDispatchSyncOnOtherThread(^{
|
||||
XCTAssertThrows(node.alpha);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)testThatReadingABridgedLayerPropertyInBackgroundThrowsAnException
|
||||
{
|
||||
ASDisplayNode *node = [ASDisplayNode new];
|
||||
[node view];
|
||||
ASDispatchSyncOnOtherThread(^{
|
||||
XCTAssertThrows(node.contentsScale);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)testThatManuallyFlushingTheSyncControllerImmediatelyAppliesChanges
|
||||
{
|
||||
ASPendingStateController *ctrl = [ASPendingStateController sharedInstance];
|
||||
ASDisplayNode *node = [ASDisplayNode new];
|
||||
[node view];
|
||||
XCTAssertEqual(node.alpha, 1);
|
||||
ASDispatchSyncOnOtherThread(^{
|
||||
node.alpha = 0;
|
||||
});
|
||||
XCTAssertEqual(node.alpha, 1);
|
||||
[ctrl flush];
|
||||
XCTAssertEqual(node.alpha, 0);
|
||||
XCTAssertFalse(ctrl.test_isFlushScheduled);
|
||||
}
|
||||
|
||||
- (void)testThatFlushingTheControllerInBackgroundThrows
|
||||
{
|
||||
ASPendingStateController *ctrl = [ASPendingStateController sharedInstance];
|
||||
ASDisplayNode *node = [ASDisplayNode new];
|
||||
[node view];
|
||||
XCTAssertEqual(node.alpha, 1);
|
||||
ASDispatchSyncOnOtherThread(^{
|
||||
node.alpha = 0;
|
||||
XCTAssertThrows([ctrl flush]);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)testThatSettingABridgedPropertyOnMainThreadPassesDirectlyToView
|
||||
{
|
||||
ASPendingStateController *ctrl = [ASPendingStateController sharedInstance];
|
||||
ASDisplayNode *node = [ASDisplayNode new];
|
||||
XCTAssertFalse(ASDisplayNodeGetPendingState(node).hasChanges);
|
||||
[node view];
|
||||
XCTAssertEqual(node.alpha, 1);
|
||||
node.alpha = 0;
|
||||
XCTAssertEqual(node.view.alpha, 0);
|
||||
XCTAssertEqual(node.alpha, 0);
|
||||
XCTAssertFalse(ASDisplayNodeGetPendingState(node).hasChanges);
|
||||
XCTAssertFalse(ctrl.test_isFlushScheduled);
|
||||
}
|
||||
|
||||
- (void)testThatCallingSetNeedsLayoutFromBackgroundCausesItToHappenLater
|
||||
{
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] initWithViewClass:ASBridgedPropertiesTestView.class];
|
||||
ASBridgedPropertiesTestView *view = (ASBridgedPropertiesTestView *)node.view;
|
||||
XCTAssertFalse(view.receivedSetNeedsLayout);
|
||||
ASDispatchSyncOnOtherThread(^{
|
||||
XCTAssertNoThrow([node setNeedsLayout]);
|
||||
});
|
||||
XCTAssertFalse(view.receivedSetNeedsLayout);
|
||||
[self waitForMainDispatchQueueToFlush];
|
||||
XCTAssertTrue(view.receivedSetNeedsLayout);
|
||||
}
|
||||
|
||||
- (void)testThatCallingSetNeedsLayoutOnACellNodeFromBackgroundIsSafe
|
||||
{
|
||||
ASCellNode *node = [ASCellNode new];
|
||||
[node view];
|
||||
ASDispatchSyncOnOtherThread(^{
|
||||
XCTAssertNoThrow([node setNeedsLayout]);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)testThatCallingSetNeedsDisplayFromBackgroundCausesItToHappenLater
|
||||
{
|
||||
ASDisplayNode *node = [ASDisplayNode new];
|
||||
[node.layer displayIfNeeded];
|
||||
XCTAssertFalse(node.layer.needsDisplay);
|
||||
ASDispatchSyncOnOtherThread(^{
|
||||
XCTAssertNoThrow([node setNeedsDisplay]);
|
||||
});
|
||||
XCTAssertFalse(node.layer.needsDisplay);
|
||||
[self waitForMainDispatchQueueToFlush];
|
||||
XCTAssertTrue(node.layer.needsDisplay);
|
||||
}
|
||||
|
||||
/// [XCTExpectation expectationWithPredicate:] should handle this
|
||||
/// but under Xcode 7.2.1 its polling interval is 1 second
|
||||
/// which makes the tests really slow and I'm impatient.
|
||||
- (void)waitForMainDispatchQueueToFlush
|
||||
{
|
||||
__block BOOL done = NO;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
done = YES;
|
||||
});
|
||||
while (!done) {
|
||||
[NSRunLoop.mainRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
//
|
||||
// ASButtonNodeTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASButtonNode.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
|
||||
@interface ASButtonNodeTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation ASButtonNodeTests
|
||||
|
||||
- (void)testAccessibility
|
||||
{
|
||||
// Setup a button with some title.
|
||||
ASButtonNode *buttonNode = nil;
|
||||
buttonNode = [[ASButtonNode alloc] init];
|
||||
NSString *title = @"foo";
|
||||
[buttonNode setTitle:title withFont:nil withColor:nil forState:UIControlStateNormal];
|
||||
|
||||
// Verify accessibility properties.
|
||||
XCTAssertTrue(buttonNode.accessibilityTraits == UIAccessibilityTraitButton,
|
||||
@"Should have button accessibility trait, instead has %llu",
|
||||
buttonNode.accessibilityTraits);
|
||||
XCTAssertTrue(buttonNode.defaultAccessibilityTraits == UIAccessibilityTraitButton,
|
||||
@"Default accessibility traits should return button accessibility trait, instead "
|
||||
@"returns %llu",
|
||||
buttonNode.defaultAccessibilityTraits);
|
||||
XCTAssertTrue([buttonNode.accessibilityLabel isEqualToString:title],
|
||||
@"Accessibility label is incorrectly set to \n%@\n when it should be \n%@\n",
|
||||
buttonNode.accessibilityLabel, title);
|
||||
XCTAssertTrue([buttonNode.defaultAccessibilityLabel isEqualToString:title],
|
||||
@"Default accessibility label incorrectly returns \n%@\n when it should be \n%@\n",
|
||||
buttonNode.defaultAccessibilityLabel, title);
|
||||
|
||||
// Disable the button and verify that accessibility traits has been updated correctly.
|
||||
buttonNode.enabled = NO;
|
||||
UIAccessibilityTraits disabledButtonTrait = UIAccessibilityTraitButton | UIAccessibilityTraitNotEnabled;
|
||||
XCTAssertTrue(buttonNode.accessibilityTraits == disabledButtonTrait,
|
||||
@"Should have disabled button accessibility trait, instead has %llu",
|
||||
buttonNode.accessibilityTraits);
|
||||
XCTAssertTrue(buttonNode.defaultAccessibilityTraits == disabledButtonTrait,
|
||||
@"Default accessibility traits should return disabled button accessibility trait, "
|
||||
@"instead returns %llu",
|
||||
buttonNode.defaultAccessibilityTraits);
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
//
|
||||
// ASCALayerTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <OCMock/OCMock.h>
|
||||
|
||||
/**
|
||||
* Tests that confirm what we know about Core Animation behavior.
|
||||
*
|
||||
* These tests are not run during the normal test action. You can run them yourself
|
||||
* to investigate and confirm CA behavior.
|
||||
*/
|
||||
@interface ASCALayerTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
#define DeclareLayerAndSublayer() \
|
||||
CALayer *realSublayer = [CALayer layer]; \
|
||||
id layer = [OCMockObject partialMockForObject:[CALayer layer]]; \
|
||||
id sublayer = [OCMockObject partialMockForObject:realSublayer]; \
|
||||
[layer addSublayer:realSublayer];
|
||||
|
||||
@implementation ASCALayerTests
|
||||
|
||||
- (void)testThatLayerBeginsWithCleanLayout
|
||||
{
|
||||
XCTAssertFalse([CALayer layer].needsLayout);
|
||||
}
|
||||
|
||||
- (void)testThatAddingSublayersDirtysLayout
|
||||
{
|
||||
CALayer *layer = [CALayer layer];
|
||||
[layer addSublayer:[CALayer layer]];
|
||||
XCTAssertTrue([layer needsLayout]);
|
||||
}
|
||||
|
||||
- (void)testThatRemovingSublayersDirtysLayout
|
||||
{
|
||||
DeclareLayerAndSublayer();
|
||||
[layer layoutIfNeeded];
|
||||
XCTAssertFalse([layer needsLayout]);
|
||||
[sublayer removeFromSuperlayer];
|
||||
XCTAssertTrue([layer needsLayout]);
|
||||
}
|
||||
|
||||
- (void)testDirtySublayerLayoutDoesntDirtySuperlayer
|
||||
{
|
||||
DeclareLayerAndSublayer();
|
||||
[layer layoutIfNeeded];
|
||||
|
||||
// Dirtying sublayer doesn't dirty superlayer.
|
||||
[sublayer setNeedsLayout];
|
||||
XCTAssertTrue([sublayer needsLayout]);
|
||||
XCTAssertFalse([layer needsLayout]);
|
||||
[[[sublayer expect] andForwardToRealObject] layoutSublayers];
|
||||
// NOTE: We specifically don't expect layer to get -layoutSublayers
|
||||
[sublayer layoutIfNeeded];
|
||||
[sublayer verify];
|
||||
[layer verify];
|
||||
}
|
||||
|
||||
- (void)testDirtySuperlayerLayoutDoesntDirtySublayerLayout
|
||||
{
|
||||
DeclareLayerAndSublayer();
|
||||
[layer layoutIfNeeded];
|
||||
|
||||
// Dirtying superlayer doesn't dirty sublayer.
|
||||
[layer setNeedsLayout];
|
||||
XCTAssertTrue([layer needsLayout]);
|
||||
XCTAssertFalse([sublayer needsLayout]);
|
||||
[[[layer expect] andForwardToRealObject] layoutSublayers];
|
||||
// NOTE: We specifically don't expect sublayer to get -layoutSublayers
|
||||
[layer layoutIfNeeded];
|
||||
[sublayer verify];
|
||||
[layer verify];
|
||||
}
|
||||
|
||||
- (void)testDirtyHierarchyIsLaidOutTopDown
|
||||
{
|
||||
DeclareLayerAndSublayer();
|
||||
[sublayer setNeedsLayout];
|
||||
|
||||
XCTAssertTrue([layer needsLayout]);
|
||||
XCTAssertTrue([sublayer needsLayout]);
|
||||
|
||||
__block BOOL superlayerLaidOut = NO;
|
||||
[[[[layer expect] andDo:^(NSInvocation *i) {
|
||||
superlayerLaidOut = YES;
|
||||
}] andForwardToRealObject] layoutSublayers];
|
||||
|
||||
[[[[sublayer expect] andDo:^(NSInvocation *i) {
|
||||
XCTAssertTrue(superlayerLaidOut);
|
||||
}] andForwardToRealObject] layoutSublayers];
|
||||
|
||||
[layer layoutIfNeeded];
|
||||
[sublayer verify];
|
||||
[layer verify];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
//
|
||||
// ASCenterLayoutSpecSnapshotTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutSpecSnapshotTestsHelper.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASBackgroundLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASCenterLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASStackLayoutSpec.h>
|
||||
|
||||
static const ASSizeRange kSize = {{100, 120}, {320, 160}};
|
||||
|
||||
@interface ASCenterLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase
|
||||
@end
|
||||
|
||||
@implementation ASCenterLayoutSpecSnapshotTests
|
||||
|
||||
- (void)testWithOptions
|
||||
{
|
||||
[self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone sizingOptions:{}];
|
||||
[self testWithCenteringOptions:ASCenterLayoutSpecCenteringXY sizingOptions:{}];
|
||||
[self testWithCenteringOptions:ASCenterLayoutSpecCenteringX sizingOptions:{}];
|
||||
[self testWithCenteringOptions:ASCenterLayoutSpecCenteringY sizingOptions:{}];
|
||||
}
|
||||
|
||||
- (void)testWithSizingOptions
|
||||
{
|
||||
[self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone
|
||||
sizingOptions:ASCenterLayoutSpecSizingOptionDefault];
|
||||
[self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone
|
||||
sizingOptions:ASCenterLayoutSpecSizingOptionMinimumX];
|
||||
[self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone
|
||||
sizingOptions:ASCenterLayoutSpecSizingOptionMinimumY];
|
||||
[self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone
|
||||
sizingOptions:ASCenterLayoutSpecSizingOptionMinimumXY];
|
||||
}
|
||||
|
||||
- (void)testWithCenteringOptions:(ASCenterLayoutSpecCenteringOptions)options
|
||||
sizingOptions:(ASCenterLayoutSpecSizingOptions)sizingOptions
|
||||
{
|
||||
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
|
||||
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor greenColor], CGSizeMake(70, 100));
|
||||
|
||||
ASLayoutSpec *layoutSpec =
|
||||
[ASBackgroundLayoutSpec
|
||||
backgroundLayoutSpecWithChild:
|
||||
[ASCenterLayoutSpec
|
||||
centerLayoutSpecWithCenteringOptions:options
|
||||
sizingOptions:sizingOptions
|
||||
child:foregroundNode]
|
||||
background:backgroundNode];
|
||||
|
||||
[self testLayoutSpec:layoutSpec
|
||||
sizeRange:kSize
|
||||
subnodes:@[backgroundNode, foregroundNode]
|
||||
identifier:suffixForCenteringOptions(options, sizingOptions)];
|
||||
}
|
||||
|
||||
static NSString *suffixForCenteringOptions(ASCenterLayoutSpecCenteringOptions centeringOptions,
|
||||
ASCenterLayoutSpecSizingOptions sizingOptinos)
|
||||
{
|
||||
NSMutableString *suffix = [NSMutableString string];
|
||||
|
||||
if ((centeringOptions & ASCenterLayoutSpecCenteringX) != 0) {
|
||||
[suffix appendString:@"CenteringX"];
|
||||
}
|
||||
|
||||
if ((centeringOptions & ASCenterLayoutSpecCenteringY) != 0) {
|
||||
[suffix appendString:@"CenteringY"];
|
||||
}
|
||||
|
||||
if ((sizingOptinos & ASCenterLayoutSpecSizingOptionMinimumX) != 0) {
|
||||
[suffix appendString:@"SizingMinimumX"];
|
||||
}
|
||||
|
||||
if ((sizingOptinos & ASCenterLayoutSpecSizingOptionMinimumY) != 0) {
|
||||
[suffix appendString:@"SizingMinimumY"];
|
||||
}
|
||||
|
||||
return suffix;
|
||||
}
|
||||
|
||||
- (void)testMinimumSizeRangeIsGivenToChildWhenNotCentering
|
||||
{
|
||||
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
|
||||
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor], CGSizeMake(10, 10));
|
||||
foregroundNode.style.flexGrow = 1;
|
||||
|
||||
ASCenterLayoutSpec *layoutSpec =
|
||||
[ASCenterLayoutSpec
|
||||
centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringNone
|
||||
sizingOptions:{}
|
||||
child:
|
||||
[ASBackgroundLayoutSpec
|
||||
backgroundLayoutSpecWithChild:
|
||||
[ASStackLayoutSpec
|
||||
stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical
|
||||
spacing:0
|
||||
justifyContent:ASStackLayoutJustifyContentStart
|
||||
alignItems:ASStackLayoutAlignItemsStart
|
||||
children:@[foregroundNode]]
|
||||
background:backgroundNode]];
|
||||
|
||||
[self testLayoutSpec:layoutSpec sizeRange:kSize subnodes:@[backgroundNode, foregroundNode] identifier:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,363 +0,0 @@
|
|||
//
|
||||
// ASCollectionModernDataSourceTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <OCMock/OCMock.h>
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <AsyncDisplayKit/NSIndexSet+ASHelpers.h>
|
||||
#import "OCMockObject+ASAdditions.h"
|
||||
#import "ASTestCase.h"
|
||||
|
||||
@interface ASCollectionModernDataSourceTests : ASTestCase
|
||||
@end
|
||||
|
||||
@interface ASTestCellNode : ASCellNode
|
||||
@end
|
||||
|
||||
@interface ASTestSection : NSObject <ASSectionContext>
|
||||
@property (nonatomic, readonly) NSMutableArray *nodeModels;
|
||||
@end
|
||||
|
||||
@implementation ASCollectionModernDataSourceTests {
|
||||
@private
|
||||
id mockDataSource;
|
||||
UIWindow *window;
|
||||
UIViewController *viewController;
|
||||
ASCollectionNode *collectionNode;
|
||||
NSMutableArray<ASTestSection *> *sections;
|
||||
}
|
||||
|
||||
- (void)setUp {
|
||||
[super setUp];
|
||||
// Default is 2 sections: 2 items in first, 1 item in second.
|
||||
sections = [NSMutableArray array];
|
||||
[sections addObject:[ASTestSection new]];
|
||||
[sections[0].nodeModels addObject:[NSObject new]];
|
||||
[sections[0].nodeModels addObject:[NSObject new]];
|
||||
[sections addObject:[ASTestSection new]];
|
||||
[sections[1].nodeModels addObject:[NSObject new]];
|
||||
window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
|
||||
viewController = [[UIViewController alloc] init];
|
||||
|
||||
window.rootViewController = viewController;
|
||||
[window makeKeyAndVisible];
|
||||
collectionNode = [[ASCollectionNode alloc] initWithCollectionViewLayout:[UICollectionViewFlowLayout new]];
|
||||
collectionNode.frame = viewController.view.bounds;
|
||||
collectionNode.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
[viewController.view addSubnode:collectionNode];
|
||||
|
||||
mockDataSource = OCMStrictProtocolMock(@protocol(ASCollectionDataSource));
|
||||
[mockDataSource addImplementedOptionalProtocolMethods:
|
||||
@selector(numberOfSectionsInCollectionNode:),
|
||||
@selector(collectionNode:numberOfItemsInSection:),
|
||||
@selector(collectionNode:nodeBlockForItemAtIndexPath:),
|
||||
@selector(collectionNode:nodeModelForItemAtIndexPath:),
|
||||
@selector(collectionNode:contextForSection:),
|
||||
nil];
|
||||
[mockDataSource setExpectationOrderMatters:YES];
|
||||
|
||||
// NOTE: Adding optionally-implemented methods after this point won't work due to ASCollectionNode selector caching.
|
||||
collectionNode.dataSource = mockDataSource;
|
||||
}
|
||||
|
||||
- (void)tearDown
|
||||
{
|
||||
[collectionNode waitUntilAllUpdatesAreProcessed];
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
#pragma mark - Test Methods
|
||||
|
||||
- (void)testInitialDataLoading
|
||||
{
|
||||
[self loadInitialData];
|
||||
}
|
||||
|
||||
- (void)testReloadingAnItem
|
||||
{
|
||||
[self loadInitialData];
|
||||
|
||||
// Reload at (0, 0)
|
||||
NSIndexPath *reloadedPath = [NSIndexPath indexPathForItem:0 inSection:0];
|
||||
|
||||
[self performUpdateReloadingSections:nil
|
||||
reloadingItems:@{ reloadedPath: [NSObject new] }
|
||||
reloadMappings:@{ reloadedPath: reloadedPath }
|
||||
insertingItems:nil
|
||||
deletingItems:nil
|
||||
skippedReloadIndexPaths:nil];
|
||||
}
|
||||
|
||||
- (void)testInsertingAnItem
|
||||
{
|
||||
[self loadInitialData];
|
||||
|
||||
// Insert at (1, 0)
|
||||
NSIndexPath *insertedPath = [NSIndexPath indexPathForItem:0 inSection:1];
|
||||
|
||||
[self performUpdateReloadingSections:nil
|
||||
reloadingItems:nil
|
||||
reloadMappings:nil
|
||||
insertingItems:@{ insertedPath: [NSObject new] }
|
||||
deletingItems:nil
|
||||
skippedReloadIndexPaths:nil];
|
||||
}
|
||||
|
||||
- (void)testReloadingAnItemWithACompatibleNodeModel
|
||||
{
|
||||
[self loadInitialData];
|
||||
|
||||
// Reload and delete together, for good measure.
|
||||
NSIndexPath *reloadedPath = [NSIndexPath indexPathForItem:1 inSection:0];
|
||||
NSIndexPath *deletedPath = [NSIndexPath indexPathForItem:0 inSection:0];
|
||||
|
||||
id nodeModel = [NSObject new];
|
||||
|
||||
// Cell node should get -canUpdateToNodeModel:
|
||||
id mockCellNode = [collectionNode nodeForItemAtIndexPath:reloadedPath];
|
||||
OCMExpect([mockCellNode canUpdateToNodeModel:nodeModel])
|
||||
.andReturn(YES);
|
||||
|
||||
[self performUpdateReloadingSections:nil
|
||||
reloadingItems:@{ reloadedPath: nodeModel }
|
||||
reloadMappings:@{ reloadedPath: [NSIndexPath indexPathForItem:0 inSection:0] }
|
||||
insertingItems:nil
|
||||
deletingItems:@[ deletedPath ]
|
||||
skippedReloadIndexPaths:@[ reloadedPath ]];
|
||||
}
|
||||
|
||||
- (void)testReloadingASection
|
||||
{
|
||||
[self loadInitialData];
|
||||
|
||||
[self performUpdateReloadingSections:@{ @0: [ASTestSection new] }
|
||||
reloadingItems:nil
|
||||
reloadMappings:nil
|
||||
insertingItems:nil
|
||||
deletingItems:nil
|
||||
skippedReloadIndexPaths:nil];
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
- (void)loadInitialData
|
||||
{
|
||||
// Count methods are called twice in a row for first data load.
|
||||
// Since -reloadData is routed through our batch update system,
|
||||
// the batch update latches the "old data source counts" if needed at -beginUpdates time
|
||||
// and then verifies them against the "new data source counts" after the updates.
|
||||
// This isn't ideal, but the cost is very small and the system works well.
|
||||
for (int i = 0; i < 2; i++) {
|
||||
// It reads all the counts
|
||||
[self expectDataSourceCountMethods];
|
||||
}
|
||||
|
||||
// It reads each section object.
|
||||
for (NSInteger section = 0; section < sections.count; section++) {
|
||||
[self expectContextMethodForSection:section];
|
||||
}
|
||||
|
||||
// It reads the contents for each item.
|
||||
for (NSInteger section = 0; section < sections.count; section++) {
|
||||
NSArray *nodeModels = sections[section].nodeModels;
|
||||
|
||||
// For each item:
|
||||
for (NSInteger i = 0; i < nodeModels.count; i++) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:section];
|
||||
[self expectNodeModelMethodForItemAtIndexPath:indexPath nodeModel:nodeModels[i]];
|
||||
[self expectNodeBlockMethodForItemAtIndexPath:indexPath];
|
||||
}
|
||||
}
|
||||
|
||||
[window layoutIfNeeded];
|
||||
|
||||
// Assert item counts & content:
|
||||
[self assertCollectionNodeContent];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds expectations for the sequence:
|
||||
*
|
||||
* numberOfSectionsInCollectionNode:
|
||||
* for section in countsArray
|
||||
* numberOfItemsInSection:
|
||||
*/
|
||||
- (void)expectDataSourceCountMethods
|
||||
{
|
||||
// -numberOfSectionsInCollectionNode
|
||||
OCMExpect([mockDataSource numberOfSectionsInCollectionNode:collectionNode])
|
||||
.andReturn(sections.count);
|
||||
|
||||
// For each section:
|
||||
// Note: Skip fast enumeration for readability.
|
||||
for (NSInteger section = 0; section < sections.count; section++) {
|
||||
OCMExpect([mockDataSource collectionNode:collectionNode numberOfItemsInSection:section])
|
||||
.andReturn(sections[section].nodeModels.count);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)expectNodeModelMethodForItemAtIndexPath:(NSIndexPath *)indexPath nodeModel:(id)nodeModel
|
||||
{
|
||||
OCMExpect([mockDataSource collectionNode:collectionNode nodeModelForItemAtIndexPath:indexPath])
|
||||
.andReturn(nodeModel);
|
||||
}
|
||||
|
||||
- (void)expectContextMethodForSection:(NSInteger)section
|
||||
{
|
||||
OCMExpect([mockDataSource collectionNode:collectionNode contextForSection:section])
|
||||
.andReturn(sections[section]);
|
||||
}
|
||||
|
||||
- (void)expectNodeBlockMethodForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
OCMExpect([mockDataSource collectionNode:collectionNode nodeBlockForItemAtIndexPath:indexPath])
|
||||
.andReturn((ASCellNodeBlock)^{
|
||||
ASCellNode *node = [ASTestCellNode new];
|
||||
// Generating multiple partial mocks of the same class is not thread-safe.
|
||||
id mockNode;
|
||||
@synchronized (NSNull.null) {
|
||||
mockNode = OCMPartialMock(node);
|
||||
}
|
||||
[mockNode setExpectationOrderMatters:YES];
|
||||
return mockNode;
|
||||
});
|
||||
}
|
||||
|
||||
/// Asserts that counts match and all view-models are up-to-date between us and collectionNode.
|
||||
- (void)assertCollectionNodeContent
|
||||
{
|
||||
// Assert section count
|
||||
XCTAssertEqual(collectionNode.numberOfSections, sections.count);
|
||||
|
||||
for (NSInteger section = 0; section < sections.count; section++) {
|
||||
ASTestSection *sectionObject = sections[section];
|
||||
NSArray *nodeModels = sectionObject.nodeModels;
|
||||
|
||||
// Assert section object
|
||||
XCTAssertEqualObjects([collectionNode contextForSection:section], sectionObject);
|
||||
|
||||
// Assert item count
|
||||
XCTAssertEqual([collectionNode numberOfItemsInSection:section], nodeModels.count);
|
||||
for (NSInteger item = 0; item < nodeModels.count; item++) {
|
||||
// Assert node model
|
||||
// Could use pointer equality but the error message is less readable.
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section];
|
||||
id nodeModel = nodeModels[indexPath.item];
|
||||
XCTAssertEqualObjects(nodeModel, [collectionNode nodeModelForItemAtIndexPath:indexPath]);
|
||||
ASCellNode *node = [collectionNode nodeForItemAtIndexPath:indexPath];
|
||||
XCTAssertEqualObjects(node.nodeModel, nodeModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the collection node, with expectations and assertions about the call-order and the correctness of the
|
||||
* new data. You should update the data source _before_ calling this method.
|
||||
*
|
||||
* skippedReloadIndexPaths are the old index paths for nodes that should use -canUpdateToNodeModel: instead of being refetched.
|
||||
*/
|
||||
- (void)performUpdateReloadingSections:(NSDictionary<NSNumber *, id> *)reloadedSections
|
||||
reloadingItems:(NSDictionary<NSIndexPath *, id> *)reloadedItems
|
||||
reloadMappings:(NSDictionary<NSIndexPath *, NSIndexPath *> *)reloadMappings
|
||||
insertingItems:(NSDictionary<NSIndexPath *, id> *)insertedItems
|
||||
deletingItems:(NSArray<NSIndexPath *> *)deletedItems
|
||||
skippedReloadIndexPaths:(NSArray<NSIndexPath *> *)skippedReloadIndexPaths
|
||||
{
|
||||
[collectionNode performBatchUpdates:^{
|
||||
// First update our data source.
|
||||
[reloadedItems enumerateKeysAndObjectsUsingBlock:^(NSIndexPath * _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
|
||||
sections[key.section].nodeModels[key.item] = obj;
|
||||
}];
|
||||
[reloadedSections enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
|
||||
sections[key.integerValue] = obj;
|
||||
}];
|
||||
|
||||
// Deletion paths, sorted descending
|
||||
for (NSIndexPath *indexPath in [deletedItems sortedArrayUsingSelector:@selector(compare:)].reverseObjectEnumerator) {
|
||||
[sections[indexPath.section].nodeModels removeObjectAtIndex:indexPath.item];
|
||||
}
|
||||
|
||||
// Insertion paths, sorted ascending.
|
||||
NSArray *insertionsSortedAcending = [insertedItems.allKeys sortedArrayUsingSelector:@selector(compare:)];
|
||||
for (NSIndexPath *indexPath in insertionsSortedAcending) {
|
||||
[sections[indexPath.section].nodeModels insertObject:insertedItems[indexPath] atIndex:indexPath.item];
|
||||
}
|
||||
|
||||
// Then update the collection node.
|
||||
NSMutableIndexSet *reloadedSectionIndexes = [NSMutableIndexSet indexSet];
|
||||
for (NSNumber *i in reloadedSections) {
|
||||
[reloadedSectionIndexes addIndex:i.integerValue];
|
||||
}
|
||||
[collectionNode reloadSections:reloadedSectionIndexes];
|
||||
[collectionNode reloadItemsAtIndexPaths:reloadedItems.allKeys];
|
||||
[collectionNode deleteItemsAtIndexPaths:deletedItems];
|
||||
[collectionNode insertItemsAtIndexPaths:insertedItems.allKeys];
|
||||
|
||||
// Before the commit, lay out our expectations.
|
||||
|
||||
// Expect it to load the new counts.
|
||||
[self expectDataSourceCountMethods];
|
||||
|
||||
// Combine reloads + inserts and expect them to load content for all of them, in ascending order.
|
||||
NSMutableDictionary<NSIndexPath *, id> *insertsPlusReloads = [[NSMutableDictionary alloc] initWithDictionary:insertedItems];
|
||||
|
||||
// Go through reloaded sections and add all their items into `insertsPlusReloads`
|
||||
[reloadedSectionIndexes enumerateIndexesUsingBlock:^(NSUInteger section, BOOL * _Nonnull stop) {
|
||||
[self expectContextMethodForSection:section];
|
||||
NSArray *nodeModels = sections[section].nodeModels;
|
||||
for (NSInteger i = 0; i < nodeModels.count; i++) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:section];
|
||||
insertsPlusReloads[indexPath] = nodeModels[i];
|
||||
}
|
||||
}];
|
||||
|
||||
[reloadedItems enumerateKeysAndObjectsUsingBlock:^(NSIndexPath * _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
|
||||
insertsPlusReloads[reloadMappings[key]] = obj;
|
||||
}];
|
||||
|
||||
for (NSIndexPath *indexPath in [insertsPlusReloads.allKeys sortedArrayUsingSelector:@selector(compare:)]) {
|
||||
[self expectNodeModelMethodForItemAtIndexPath:indexPath nodeModel:insertsPlusReloads[indexPath]];
|
||||
NSIndexPath *oldIndexPath = [reloadMappings allKeysForObject:indexPath].firstObject;
|
||||
BOOL isSkippedReload = oldIndexPath && [skippedReloadIndexPaths containsObject:oldIndexPath];
|
||||
if (!isSkippedReload) {
|
||||
[self expectNodeBlockMethodForItemAtIndexPath:indexPath];
|
||||
}
|
||||
}
|
||||
} completion:nil];
|
||||
|
||||
// Assert that the counts and node models are all correct now.
|
||||
[self assertCollectionNodeContent];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Other Objects
|
||||
|
||||
@implementation ASTestCellNode
|
||||
|
||||
- (BOOL)canUpdateToNodeModel:(id)nodeModel
|
||||
{
|
||||
// Our tests default to NO for migrating node models. We use OCMExpect to return YES when we specifically want to.
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTestSection
|
||||
@synthesize collectionView;
|
||||
@synthesize sectionName;
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_nodeModels = [NSMutableArray array];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,428 +0,0 @@
|
|||
//
|
||||
// ASCollectionViewFlowLayoutInspectorTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <OCMock/OCMock.h>
|
||||
#import "ASXCTExtensions.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASCollectionView.h>
|
||||
#import <AsyncDisplayKit/ASCollectionNode.h>
|
||||
#import <AsyncDisplayKit/ASCollectionViewFlowLayoutInspector.h>
|
||||
#import <AsyncDisplayKit/ASCellNode.h>
|
||||
#import <AsyncDisplayKit/ASCollectionView+Undeprecated.h>
|
||||
|
||||
@interface ASCollectionView (Private)
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Test Data Source
|
||||
*/
|
||||
@interface InspectorTestDataSource : NSObject <ASCollectionDataSource>
|
||||
@end
|
||||
|
||||
@implementation InspectorTestDataSource
|
||||
|
||||
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return [[ASCellNode alloc] init];
|
||||
}
|
||||
|
||||
- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return ^{ return [[ASCellNode alloc] init]; };
|
||||
}
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@protocol InspectorTestDataSourceDelegateProtocol <ASCollectionDataSource, ASCollectionDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@interface InspectorTestDataSourceDelegateWithoutNodeConstrainedSize : NSObject <InspectorTestDataSourceDelegateProtocol>
|
||||
@end
|
||||
|
||||
@implementation InspectorTestDataSourceDelegateWithoutNodeConstrainedSize
|
||||
|
||||
- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return ^{ return [[ASCellNode alloc] init]; };
|
||||
}
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASCollectionViewFlowLayoutInspectorTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Test Delegate for Header Reference Size Implementation
|
||||
*/
|
||||
@interface HeaderReferenceSizeTestDelegate : NSObject <ASCollectionDelegateFlowLayout>
|
||||
|
||||
@end
|
||||
|
||||
@implementation HeaderReferenceSizeTestDelegate
|
||||
|
||||
- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
return CGSizeMake(125.0, 125.0);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Test Delegate for Footer Reference Size Implementation
|
||||
*/
|
||||
@interface FooterReferenceSizeTestDelegate : NSObject <ASCollectionDelegateFlowLayout>
|
||||
|
||||
@end
|
||||
|
||||
@implementation FooterReferenceSizeTestDelegate
|
||||
|
||||
- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section
|
||||
{
|
||||
return CGSizeMake(125.0, 125.0);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASCollectionViewFlowLayoutInspectorTests
|
||||
|
||||
- (void)setUp {
|
||||
[super setUp];
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
#pragma mark - #collectionView:constrainedSizeForSupplementaryNodeOfKind:atIndexPath:
|
||||
|
||||
// Vertical
|
||||
|
||||
// Delegate implementation
|
||||
|
||||
- (void)testThatItReturnsAVerticalConstrainedSizeFromTheHeaderDelegateImplementation
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
|
||||
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
|
||||
|
||||
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
collectionView.asyncDelegate = delegate;
|
||||
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(collectionView.bounds.size.width, 125.0));
|
||||
|
||||
ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the values returned in the delegate implementation");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
- (void)testThatItReturnsAVerticalConstrainedSizeFromTheFooterDelegateImplementation
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
FooterReferenceSizeTestDelegate *delegate = [[FooterReferenceSizeTestDelegate alloc] init];
|
||||
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
|
||||
|
||||
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
collectionView.asyncDelegate = delegate;
|
||||
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(collectionView.bounds.size.width, 125.0));
|
||||
ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the values returned in the delegate implementation");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
// Size implementation
|
||||
|
||||
- (void)testThatItReturnsAVerticalConstrainedSizeFromTheHeaderProperty
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
|
||||
layout.headerReferenceSize = CGSizeMake(125.0, 125.0);
|
||||
|
||||
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(collectionView.bounds.size.width, 125.0));
|
||||
ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the size set on the layout");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
- (void)testThatItReturnsAVerticalConstrainedSizeFromTheFooterProperty
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
|
||||
layout.footerReferenceSize = CGSizeMake(125.0, 125.0);
|
||||
|
||||
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(collectionView.bounds.size.width, 125.0));
|
||||
ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the size set on the layout");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
// Horizontal
|
||||
|
||||
- (void)testThatItReturnsAHorizontalConstrainedSizeFromTheHeaderDelegateImplementation
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
|
||||
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
|
||||
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
collectionView.asyncDelegate = delegate;
|
||||
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(125.0, collectionView.bounds.size.height));
|
||||
ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the values returned in the delegate implementation");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
- (void)testThatItReturnsAHorizontalConstrainedSizeFromTheFooterDelegateImplementation
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
FooterReferenceSizeTestDelegate *delegate = [[FooterReferenceSizeTestDelegate alloc] init];
|
||||
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
|
||||
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
collectionView.asyncDelegate = delegate;
|
||||
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(125.0, collectionView.bounds.size.height));
|
||||
ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the values returned in the delegate implementation");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
// Size implementation
|
||||
|
||||
- (void)testThatItReturnsAHorizontalConstrainedSizeFromTheHeaderProperty
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
layout.headerReferenceSize = CGSizeMake(125.0, 125.0);
|
||||
|
||||
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(125.0, collectionView.bounds.size.width));
|
||||
ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the size set on the layout");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
- (void)testThatItReturnsAHorizontalConstrainedSizeFromTheFooterProperty
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
layout.footerReferenceSize = CGSizeMake(125.0, 125.0);
|
||||
|
||||
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(125.0, collectionView.bounds.size.height));
|
||||
ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the size set on the layout");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
- (void)testThatItReturnsZeroSizeWhenNoReferenceSizeIsImplemented
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
collectionView.asyncDelegate = delegate;
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeZero);
|
||||
XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a zero size");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
#pragma mark - #collectionView:supplementaryNodesOfKind:inSection:
|
||||
|
||||
- (void)testThatItReturnsOneWhenAValidSizeIsImplementedOnTheDelegate
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
collectionView.asyncDelegate = delegate;
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
NSUInteger count = [inspector collectionView:collectionView supplementaryNodesOfKind:UICollectionElementKindSectionHeader inSection:0];
|
||||
XCTAssert(count == 1, @"should have a header supplementary view");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
- (void)testThatItReturnsOneWhenAValidSizeIsImplementedOnTheLayout
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.footerReferenceSize = CGSizeMake(125.0, 125.0);
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
collectionView.asyncDelegate = delegate;
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
NSUInteger count = [inspector collectionView:collectionView supplementaryNodesOfKind:UICollectionElementKindSectionFooter inSection:0];
|
||||
XCTAssert(count == 1, @"should have a footer supplementary view");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
- (void)testThatItReturnsNoneWhenNoReferenceSizeIsImplemented
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
collectionView.asyncDelegate = delegate;
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
NSUInteger count = [inspector collectionView:collectionView supplementaryNodesOfKind:UICollectionElementKindSectionFooter inSection:0];
|
||||
XCTAssert(count == 0, @"should not have a footer supplementary view");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
- (void)testThatItThrowsIfNodeConstrainedSizeIsImplementedOnDataSourceButNotOnDelegateLayoutInspector
|
||||
{
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
ASCollectionNode *node = [[ASCollectionNode alloc] initWithCollectionViewLayout:layout];
|
||||
ASCollectionView *collectionView = node.view;
|
||||
|
||||
id dataSourceAndDelegate = [OCMockObject mockForProtocol:@protocol(InspectorTestDataSourceDelegateProtocol)];
|
||||
ASSizeRange constrainedSize = ASSizeRangeMake(CGSizeZero, CGSizeZero);
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
|
||||
NSValue *value = [NSValue value:&constrainedSize withObjCType:@encode(ASSizeRange)];
|
||||
[[[dataSourceAndDelegate stub] andReturnValue:value] collectionNode:node constrainedSizeForItemAtIndexPath:indexPath];
|
||||
node.dataSource = dataSourceAndDelegate;
|
||||
|
||||
id delegate = [InspectorTestDataSourceDelegateWithoutNodeConstrainedSize new];
|
||||
node.delegate = delegate;
|
||||
|
||||
ASCollectionViewLayoutInspector *inspector = [[ASCollectionViewLayoutInspector alloc] init];
|
||||
|
||||
collectionView.layoutInspector = inspector;
|
||||
XCTAssertThrows([inspector collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]);
|
||||
|
||||
node.delegate = dataSourceAndDelegate;
|
||||
XCTAssertNoThrow([inspector collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]);
|
||||
}
|
||||
|
||||
- (void)testThatItThrowsIfNodeConstrainedSizeIsImplementedOnDataSourceButNotOnDelegateFlowLayoutInspector
|
||||
{
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
|
||||
ASCollectionNode *node = [[ASCollectionNode alloc] initWithCollectionViewLayout:layout];
|
||||
ASCollectionView *collectionView = node.view;
|
||||
id dataSourceAndDelegate = [OCMockObject mockForProtocol:@protocol(InspectorTestDataSourceDelegateProtocol)];
|
||||
ASSizeRange constrainedSize = ASSizeRangeMake(CGSizeZero, CGSizeZero);
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
|
||||
NSValue *value = [NSValue value:&constrainedSize withObjCType:@encode(ASSizeRange)];
|
||||
|
||||
[[[dataSourceAndDelegate stub] andReturnValue:value] collectionNode:node constrainedSizeForItemAtIndexPath:indexPath];
|
||||
node.dataSource = dataSourceAndDelegate;
|
||||
id delegate = [InspectorTestDataSourceDelegateWithoutNodeConstrainedSize new];
|
||||
|
||||
node.delegate = delegate;
|
||||
ASCollectionViewFlowLayoutInspector *inspector = collectionView.layoutInspector;
|
||||
|
||||
XCTAssertThrows([inspector collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]);
|
||||
|
||||
node.delegate = dataSourceAndDelegate;
|
||||
XCTAssertNoThrow([inspector collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]);
|
||||
}
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,217 +0,0 @@
|
|||
//
|
||||
// ASCollectionViewThrashTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <AsyncDisplayKit/ASCollectionView.h>
|
||||
#import <stdatomic.h>
|
||||
|
||||
#import "ASThrashUtility.h"
|
||||
|
||||
@interface ASCollectionViewThrashTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASCollectionViewThrashTests
|
||||
{
|
||||
// The current update, which will be logged in case of a failure.
|
||||
ASThrashUpdate *_update;
|
||||
BOOL _failed;
|
||||
}
|
||||
|
||||
- (void)tearDown
|
||||
{
|
||||
if (_failed && _update != nil) {
|
||||
NSLog(@"Failed update %@: %@", _update, _update.logFriendlyBase64Representation);
|
||||
}
|
||||
_failed = NO;
|
||||
_update = nil;
|
||||
}
|
||||
|
||||
// NOTE: Despite the documentation, this is not always called if an exception is caught.
|
||||
- (void)recordFailureWithDescription:(NSString *)description inFile:(NSString *)filePath atLine:(NSUInteger)lineNumber expected:(BOOL)expected
|
||||
{
|
||||
_failed = YES;
|
||||
[super recordFailureWithDescription:description inFile:filePath atLine:lineNumber expected:expected];
|
||||
}
|
||||
|
||||
- (void)verifyDataSource:(ASThrashDataSource *)ds
|
||||
{
|
||||
CollectionView *collectionView = ds.collectionView;
|
||||
NSArray <ASThrashTestSection *> *data = [ds data];
|
||||
for (NSInteger i = 0; i < collectionView.numberOfSections; i++) {
|
||||
XCTAssertEqual([collectionView numberOfItemsInSection:i], data[i].items.count);
|
||||
|
||||
for (NSInteger j = 0; j < [collectionView numberOfItemsInSection:i]; j++) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:j inSection:i];
|
||||
ASThrashTestItem *item = data[i].items[j];
|
||||
ASThrashTestNode *node = (ASThrashTestNode *)[collectionView nodeForItemAtIndexPath:indexPath];
|
||||
XCTAssertEqualObjects(node.item, item, @"Wrong node at index path %@", indexPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Test Methods
|
||||
|
||||
- (void)testInitialDataRead
|
||||
{
|
||||
ASThrashDataSource *ds = [[ASThrashDataSource alloc] initCollectionViewDataSourceWithData:[ASThrashTestSection sectionsWithCount:kInitialSectionCount]];
|
||||
[self verifyDataSource:ds];
|
||||
}
|
||||
|
||||
/// Replays the Base64 representation of an ASThrashUpdate from "ASThrashTestRecordedCase" file
|
||||
- (void)testRecordedThrashCase
|
||||
{
|
||||
NSURL *caseURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"ASThrashTestRecordedCase" withExtension:nil subdirectory:@"TestResources"];
|
||||
NSString *base64 = [NSString stringWithContentsOfURL:caseURL encoding:NSUTF8StringEncoding error:NULL];
|
||||
|
||||
_update = [ASThrashUpdate thrashUpdateWithBase64String:base64];
|
||||
if (_update == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
ASThrashDataSource *ds = [[ASThrashDataSource alloc] initCollectionViewDataSourceWithData:_update.oldData];
|
||||
[self applyUpdateUsingBatchUpdates:_update
|
||||
toDataSource:ds
|
||||
animated:NO
|
||||
useXCTestWait:YES];
|
||||
[self verifyDataSource:ds];
|
||||
}
|
||||
|
||||
- (void)testThrashingWildly
|
||||
{
|
||||
for (NSInteger i = 0; i < kThrashingIterationCount; i++) {
|
||||
[self setUp];
|
||||
@autoreleasepool {
|
||||
NSArray *sections = [ASThrashTestSection sectionsWithCount:kInitialSectionCount];
|
||||
_update = [[ASThrashUpdate alloc] initWithData:sections];
|
||||
ASThrashDataSource *ds = [[ASThrashDataSource alloc] initCollectionViewDataSourceWithData:sections];
|
||||
|
||||
[self applyUpdateUsingBatchUpdates:_update
|
||||
toDataSource:ds
|
||||
animated:NO
|
||||
useXCTestWait:NO];
|
||||
[self verifyDataSource:ds];
|
||||
[self expectationForPredicate:[ds predicateForDeallocatedHierarchy] evaluatedWithObject:(id)kCFNull handler:nil];
|
||||
}
|
||||
[self waitForExpectationsWithTimeout:3 handler:nil];
|
||||
|
||||
[self tearDown];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testThrashingWildlyOnSameCollectionView
|
||||
{
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"last test ran"];
|
||||
ASThrashDataSource *ds = [[ASThrashDataSource alloc] initCollectionViewDataSourceWithData:nil];
|
||||
for (NSInteger i = 0; i < 1000; i++) {
|
||||
[self setUp];
|
||||
@autoreleasepool {
|
||||
NSArray *sections = [ASThrashTestSection sectionsWithCount:kInitialSectionCount];
|
||||
_update = [[ASThrashUpdate alloc] initWithData:sections];
|
||||
[ds setData:sections];
|
||||
[ds.collectionView reloadData];
|
||||
|
||||
[self applyUpdateUsingBatchUpdates:_update
|
||||
toDataSource:ds
|
||||
animated:NO
|
||||
useXCTestWait:NO];
|
||||
[self verifyDataSource:ds];
|
||||
if (i == 999) {
|
||||
[expectation fulfill];
|
||||
}
|
||||
}
|
||||
|
||||
[self tearDown];
|
||||
}
|
||||
[self waitForExpectationsWithTimeout:3 handler:nil];
|
||||
}
|
||||
|
||||
- (void)testThrashingWildlyDispatchWildly
|
||||
{
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"last test ran"];
|
||||
for (NSInteger i = 0; i < kThrashingIterationCount; i++) {
|
||||
[self setUp];
|
||||
@autoreleasepool {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSArray *sections = [ASThrashTestSection sectionsWithCount:kInitialSectionCount];
|
||||
_update = [[ASThrashUpdate alloc] initWithData:sections];
|
||||
ASThrashDataSource *ds = [[ASThrashDataSource alloc] initCollectionViewDataSourceWithData:sections];
|
||||
|
||||
[self applyUpdateUsingBatchUpdates:_update
|
||||
toDataSource:ds
|
||||
animated:NO
|
||||
useXCTestWait:NO];
|
||||
[self verifyDataSource:ds];
|
||||
if (i == kThrashingIterationCount-1) {
|
||||
[expectation fulfill];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[self tearDown];
|
||||
}
|
||||
|
||||
[self waitForExpectationsWithTimeout:100 handler:nil];
|
||||
}
|
||||
|
||||
#pragma mark Helpers
|
||||
|
||||
- (void)applyUpdateUsingBatchUpdates:(ASThrashUpdate *)update
|
||||
toDataSource:(ASThrashDataSource *)dataSource animated:(BOOL)animated
|
||||
useXCTestWait:(BOOL)wait
|
||||
{
|
||||
CollectionView *collectionView = dataSource.collectionView;
|
||||
|
||||
XCTestExpectation *expectation;
|
||||
if (wait) {
|
||||
expectation = [self expectationWithDescription:@"Wait for collection view to update"];
|
||||
}
|
||||
|
||||
void (^updateBlock)() = ^ void (){
|
||||
dataSource.data = update.data;
|
||||
|
||||
[collectionView insertSections:update.insertedSectionIndexes];
|
||||
[collectionView deleteSections:update.deletedSectionIndexes];
|
||||
[collectionView reloadSections:update.replacedSectionIndexes];
|
||||
|
||||
[update.insertedItemIndexes enumerateObjectsUsingBlock:^(NSMutableIndexSet * _Nonnull indexes, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
NSArray *indexPaths = [indexes indexPathsInSection:idx];
|
||||
[collectionView insertItemsAtIndexPaths:indexPaths];
|
||||
}];
|
||||
|
||||
[update.deletedItemIndexes enumerateObjectsUsingBlock:^(NSMutableIndexSet * _Nonnull indexes, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
NSArray *indexPaths = [indexes indexPathsInSection:idx];
|
||||
[collectionView deleteItemsAtIndexPaths:indexPaths];
|
||||
}];
|
||||
|
||||
[update.replacedItemIndexes enumerateObjectsUsingBlock:^(NSMutableIndexSet * _Nonnull indexes, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
NSArray *indexPaths = [indexes indexPathsInSection:idx];
|
||||
[collectionView reloadItemsAtIndexPaths:indexPaths];
|
||||
}];
|
||||
};
|
||||
|
||||
@try {
|
||||
[collectionView performBatchAnimated:animated
|
||||
updates:updateBlock
|
||||
completion:^(BOOL finished) {
|
||||
[expectation fulfill];
|
||||
}];
|
||||
} @catch (NSException *exception) {
|
||||
_failed = YES;
|
||||
XCTFail("TEST FAILED");
|
||||
@throw exception;
|
||||
}
|
||||
|
||||
if (wait) {
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
//
|
||||
// ASCollectionsTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/ASCollections.h>
|
||||
|
||||
@interface ASCollectionsTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASCollectionsTests
|
||||
|
||||
- (void)testTransferArray {
|
||||
id objs[2];
|
||||
objs[0] = [NSObject new];
|
||||
id o0 = objs[0];
|
||||
objs[1] = [NSObject new];
|
||||
__weak id w0 = objs[0];
|
||||
__weak id w1 = objs[1];
|
||||
CFTypeRef cf0 = (__bridge CFTypeRef)objs[0];
|
||||
CFTypeRef cf1 = (__bridge CFTypeRef)objs[1];
|
||||
XCTAssertEqual(CFGetRetainCount(cf0), 2);
|
||||
XCTAssertEqual(CFGetRetainCount(cf1), 1);
|
||||
NSArray *arr = [NSArray arrayByTransferring:objs count:2];
|
||||
XCTAssertNil(objs[0]);
|
||||
XCTAssertNil(objs[1]);
|
||||
XCTAssertEqual(CFGetRetainCount(cf0), 2);
|
||||
XCTAssertEqual(CFGetRetainCount(cf1), 1);
|
||||
NSArray *immutableCopy = [arr copy];
|
||||
XCTAssertEqual(immutableCopy, arr);
|
||||
XCTAssertEqual(CFGetRetainCount(cf0), 2);
|
||||
XCTAssertEqual(CFGetRetainCount(cf1), 1);
|
||||
NSMutableArray *mc = [arr mutableCopy];
|
||||
XCTAssertEqual(CFGetRetainCount(cf0), 3);
|
||||
XCTAssertEqual(CFGetRetainCount(cf1), 2);
|
||||
arr = nil;
|
||||
immutableCopy = nil;
|
||||
XCTAssertEqual(CFGetRetainCount(cf0), 2);
|
||||
XCTAssertEqual(CFGetRetainCount(cf1), 1);
|
||||
[mc removeObjectAtIndex:0];
|
||||
XCTAssertEqual(CFGetRetainCount(cf0), 1);
|
||||
XCTAssertEqual(CFGetRetainCount(cf1), 1);
|
||||
[mc removeObjectAtIndex:0];
|
||||
XCTAssertEqual(CFGetRetainCount(cf0), 1);
|
||||
XCTAssertNil(w1);
|
||||
o0 = nil;
|
||||
XCTAssertNil(w0);
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
//
|
||||
// ASConfigurationTests.m
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASAvailability.h>
|
||||
#import <AsyncDisplayKit/ASConfiguration.h>
|
||||
#import <AsyncDisplayKit/ASConfigurationDelegate.h>
|
||||
#import <AsyncDisplayKit/ASConfigurationInternal.h>
|
||||
|
||||
#import "ASTestCase.h"
|
||||
|
||||
static ASExperimentalFeatures features[] = {
|
||||
ASExperimentalGraphicsContexts,
|
||||
#if AS_ENABLE_TEXTNODE
|
||||
ASExperimentalTextNode,
|
||||
#endif
|
||||
ASExperimentalInterfaceStateCoalescing,
|
||||
ASExperimentalUnfairLock,
|
||||
ASExperimentalLayerDefaults,
|
||||
ASExperimentalCollectionTeardown,
|
||||
ASExperimentalFramesetterCache,
|
||||
ASExperimentalSkipClearData,
|
||||
ASExperimentalDidEnterPreloadSkipASMLayout,
|
||||
ASExperimentalDisableAccessibilityCache,
|
||||
ASExperimentalDispatchApply,
|
||||
ASExperimentalImageDownloaderPriority,
|
||||
ASExperimentalTextDrawing
|
||||
};
|
||||
|
||||
@interface ASConfigurationTests : ASTestCase <ASConfigurationDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASConfigurationTests {
|
||||
void (^onActivate)(ASConfigurationTests *self, ASExperimentalFeatures feature);
|
||||
}
|
||||
|
||||
+ (NSArray *)names {
|
||||
return @[
|
||||
@"exp_graphics_contexts",
|
||||
@"exp_text_node",
|
||||
@"exp_interface_state_coalesce",
|
||||
@"exp_unfair_lock",
|
||||
@"exp_infer_layer_defaults",
|
||||
@"exp_collection_teardown",
|
||||
@"exp_framesetter_cache",
|
||||
@"exp_skip_clear_data",
|
||||
@"exp_did_enter_preload_skip_asm_layout",
|
||||
@"exp_disable_a11y_cache",
|
||||
@"exp_dispatch_apply",
|
||||
@"exp_image_downloader_priority",
|
||||
@"exp_text_drawing"
|
||||
];
|
||||
}
|
||||
|
||||
- (ASExperimentalFeatures)allFeatures {
|
||||
ASExperimentalFeatures allFeatures = 0;
|
||||
for (int i = 0; i < sizeof(features)/sizeof(ASExperimentalFeatures); i++) {
|
||||
allFeatures |= features[i];
|
||||
}
|
||||
return allFeatures;
|
||||
}
|
||||
|
||||
#if AS_ENABLE_TEXTNODE
|
||||
|
||||
- (void)testExperimentalFeatureConfig
|
||||
{
|
||||
// Set the config
|
||||
ASConfiguration *config = [[ASConfiguration alloc] initWithDictionary:nil];
|
||||
config.experimentalFeatures = ASExperimentalGraphicsContexts;
|
||||
config.delegate = self;
|
||||
[ASConfigurationManager test_resetWithConfiguration:config];
|
||||
|
||||
// Set an expectation for a callback, and assert we only get one.
|
||||
XCTestExpectation *e = [self expectationWithDescription:@"Callbacks done."];
|
||||
e.expectedFulfillmentCount = 2;
|
||||
e.assertForOverFulfill = YES;
|
||||
onActivate = ^(ASConfigurationTests *self, ASExperimentalFeatures feature) {
|
||||
[e fulfill];
|
||||
};
|
||||
|
||||
// Now activate the graphics experiment and expect it works.
|
||||
XCTAssertTrue(ASActivateExperimentalFeature(ASExperimentalGraphicsContexts));
|
||||
// We should get a callback here
|
||||
// Now activate text node and expect it fails.
|
||||
XCTAssertFalse(ASActivateExperimentalFeature(ASExperimentalTextNode));
|
||||
// But we should get another callback.
|
||||
[self waitForExpectationsWithTimeout:3 handler:nil];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
- (void)textureDidActivateExperimentalFeatures:(ASExperimentalFeatures)feature
|
||||
{
|
||||
if (onActivate) {
|
||||
onActivate(self, feature);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testMappingNamesToFlags
|
||||
{
|
||||
// Throw in a bad bit.
|
||||
ASExperimentalFeatures allFeatures = [self allFeatures];
|
||||
ASExperimentalFeatures featuresWithBadBit = allFeatures | (1 << 22);
|
||||
NSArray *expectedNames = [ASConfigurationTests names];
|
||||
XCTAssertEqualObjects(expectedNames, ASExperimentalFeaturesGetNames(featuresWithBadBit));
|
||||
}
|
||||
|
||||
- (void)testMappingFlagsFromNames
|
||||
{
|
||||
// Throw in a bad name.
|
||||
NSMutableArray *allNames = [[NSMutableArray alloc] initWithArray:[ASConfigurationTests names]];
|
||||
[allNames addObject:@"__invalid_name"];
|
||||
ASExperimentalFeatures expected = [self allFeatures];
|
||||
XCTAssertEqual(expected, ASExperimentalFeaturesFromArray(allNames));
|
||||
}
|
||||
|
||||
- (void)testFlagMatchName
|
||||
{
|
||||
NSArray *names = [ASConfigurationTests names];
|
||||
for (NSInteger i = 0; i < names.count; i++) {
|
||||
XCTAssertEqual(features[i], ASExperimentalFeaturesFromArray(@[names[i]]));
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testNameMatchFlag {
|
||||
NSArray *names = [ASConfigurationTests names];
|
||||
for (NSInteger i = 0; i < names.count; i++) {
|
||||
XCTAssertEqualObjects(@[names[i]], ASExperimentalFeaturesGetNames(features[i]));
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,225 +0,0 @@
|
|||
//
|
||||
// ASControlNodeTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASControlNode.h>
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <OCMock/OCMock.h>
|
||||
|
||||
#define ACTION @selector(action)
|
||||
#define ACTION_SENDER @selector(action:)
|
||||
#define ACTION_SENDER_EVENT @selector(action:event:)
|
||||
#define EVENT ASControlNodeEventTouchUpInside
|
||||
|
||||
@interface ReceiverController : UIViewController
|
||||
@property (nonatomic) NSInteger hits;
|
||||
@end
|
||||
@implementation ReceiverController
|
||||
@end
|
||||
|
||||
@interface ASActionController : ReceiverController
|
||||
@end
|
||||
@implementation ASActionController
|
||||
- (void)action { self.hits++; }
|
||||
- (void)firstAction { }
|
||||
- (void)secondAction { }
|
||||
- (void)thirdAction { }
|
||||
@end
|
||||
|
||||
@interface ASActionSenderController : ReceiverController
|
||||
@end
|
||||
@implementation ASActionSenderController
|
||||
- (void)action:(id)sender { self.hits++; }
|
||||
@end
|
||||
|
||||
@interface ASActionSenderEventController : ReceiverController
|
||||
@end
|
||||
@implementation ASActionSenderEventController
|
||||
- (void)action:(id)sender event:(UIEvent *)event { self.hits++; }
|
||||
@end
|
||||
|
||||
@interface ASGestureController : ReceiverController
|
||||
@end
|
||||
@implementation ASGestureController
|
||||
- (void)onGesture:(UIGestureRecognizer *)recognizer { self.hits++; }
|
||||
- (void)action:(id)sender { self.hits++; }
|
||||
@end
|
||||
|
||||
@interface ASControlNodeTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASControlNodeTests
|
||||
|
||||
- (void)testActionWithoutParameters {
|
||||
ASActionController *controller = [[ASActionController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:ACTION forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssert(controller.hits == 1, @"Controller did not receive the action event");
|
||||
}
|
||||
|
||||
- (void)testActionAndSender {
|
||||
ASActionSenderController *controller = [[ASActionSenderController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:ACTION_SENDER forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssert(controller.hits == 1, @"Controller did not receive the action event");
|
||||
}
|
||||
|
||||
- (void)testActionAndSenderAndEvent {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssert(controller.hits == 1, @"Controller did not receive the action event");
|
||||
}
|
||||
|
||||
- (void)testActionWithoutTarget {
|
||||
ASActionController *controller = [[ASActionController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:nil action:ACTION forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssert(controller.hits == 1, @"Controller did not receive the action event");
|
||||
}
|
||||
|
||||
- (void)testActionAndSenderWithoutTarget {
|
||||
ASActionSenderController *controller = [[ASActionSenderController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:nil action:ACTION_SENDER forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssert(controller.hits == 1, @"Controller did not receive the action event");
|
||||
}
|
||||
|
||||
- (void)testActionAndSenderAndEventWithoutTarget {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssert(controller.hits == 1, @"Controller did not receive the action event");
|
||||
}
|
||||
|
||||
- (void)testRemoveWithoutTargetRemovesTargetlessAction {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[node removeTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssertEqual(controller.hits, 0, @"Controller did not receive exactly zero action events");
|
||||
}
|
||||
|
||||
- (void)testRemoveWithTarget {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[node removeTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssertEqual(controller.hits, 0, @"Controller did not receive exactly zero action events");
|
||||
}
|
||||
|
||||
- (void)testRemoveWithTargetRemovesAction {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[node removeTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssertEqual(controller.hits, 0, @"Controller did not receive exactly zero action events");
|
||||
}
|
||||
|
||||
- (void)testRemoveWithoutTargetRemovesTargetedAction {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[node removeTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssertEqual(controller.hits, 0, @"Controller did not receive exactly zero action events");
|
||||
}
|
||||
|
||||
- (void)testDuplicateEntriesWithoutTarget {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[node addTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssertEqual(controller.hits, 1, @"Controller did not receive exactly one action event");
|
||||
}
|
||||
|
||||
- (void)testDuplicateEntriesWithTarget {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssertEqual(controller.hits, 1, @"Controller did not receive exactly one action event");
|
||||
}
|
||||
|
||||
- (void)testDuplicateEntriesWithAndWithoutTarget {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[node addTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssertEqual(controller.hits, 2, @"Controller did not receive exactly two action events");
|
||||
}
|
||||
|
||||
- (void)testDeeperHierarchyWithoutTarget {
|
||||
ASActionController *controller = [[ASActionController alloc] init];
|
||||
UIView *view = [[UIView alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:nil action:ACTION forControlEvents:EVENT];
|
||||
[view addSubview:node.view];
|
||||
[controller.view addSubview:view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssert(controller.hits == 1, @"Controller did not receive the action event");
|
||||
}
|
||||
|
||||
- (void)testTouchesWorkWithGestures {
|
||||
ASGestureController *controller = [[ASGestureController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:@selector(action:) forControlEvents:ASControlNodeEventTouchUpInside];
|
||||
[node.view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:controller action:@selector(onGesture:)]];
|
||||
[controller.view addSubnode:node];
|
||||
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssert(controller.hits == 1, @"Controller did not receive the tap event");
|
||||
}
|
||||
|
||||
- (void)testActionsAreCalledInTheSameOrderAsTheyWereAdded {
|
||||
ASActionController *controller = [[ASActionController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:@selector(firstAction) forControlEvents:ASControlNodeEventTouchUpInside];
|
||||
[node addTarget:controller action:@selector(secondAction) forControlEvents:ASControlNodeEventTouchUpInside];
|
||||
[node addTarget:controller action:@selector(thirdAction) forControlEvents:ASControlNodeEventTouchUpInside];
|
||||
[controller.view addSubnode:node];
|
||||
|
||||
id controllerMock = [OCMockObject partialMockForObject:controller];
|
||||
[controllerMock setExpectationOrderMatters:YES];
|
||||
[[controllerMock expect] firstAction];
|
||||
[[controllerMock expect] secondAction];
|
||||
[[controllerMock expect] thirdAction];
|
||||
|
||||
[node sendActionsForControlEvents:ASControlNodeEventTouchUpInside withEvent:nil];
|
||||
|
||||
[controllerMock verify];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,215 +0,0 @@
|
|||
//
|
||||
// ASCornerLayoutSpecSnapshotTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutSpecSnapshotTestsHelper.h"
|
||||
#import <AsyncDisplayKit/ASCornerLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASBackgroundLayoutSpec.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, ASCornerLayoutSpecSnapshotTestsOffsetOption) {
|
||||
ASCornerLayoutSpecSnapshotTestsOffsetOptionCenter,
|
||||
ASCornerLayoutSpecSnapshotTestsOffsetOptionInner,
|
||||
ASCornerLayoutSpecSnapshotTestsOffsetOptionOuter,
|
||||
};
|
||||
|
||||
|
||||
@interface ASCornerLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase
|
||||
|
||||
@property (nonatomic, copy) UIColor *boxColor;
|
||||
@property (nonatomic, copy) UIColor *baseColor;
|
||||
@property (nonatomic, copy) UIColor *cornerColor;
|
||||
@property (nonatomic, copy) UIColor *contextColor;
|
||||
|
||||
@property (nonatomic) CGSize baseSize;
|
||||
@property (nonatomic) CGSize cornerSize;
|
||||
@property (nonatomic) CGSize contextSize;
|
||||
|
||||
@property (nonatomic) ASSizeRange contextSizeRange;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ASCornerLayoutSpecSnapshotTests
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
|
||||
self.recordMode = NO;
|
||||
|
||||
_boxColor = [UIColor greenColor];
|
||||
_baseColor = [UIColor blueColor];
|
||||
_cornerColor = [UIColor orangeColor];
|
||||
_contextColor = [UIColor lightGrayColor];
|
||||
|
||||
_baseSize = CGSizeMake(60, 60);
|
||||
_cornerSize = CGSizeMake(20, 20);
|
||||
_contextSize = CGSizeMake(120, 120);
|
||||
|
||||
_contextSizeRange = ASSizeRangeMake(CGSizeZero, _contextSize);
|
||||
}
|
||||
|
||||
- (void)testCornerSpecForAllLocations
|
||||
{
|
||||
ASCornerLayoutSpecSnapshotTestsOffsetOption center = ASCornerLayoutSpecSnapshotTestsOffsetOptionCenter;
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopLeft offsetOption:center wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopLeft offsetOption:center wrapsCorner:YES];
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopRight offsetOption:center wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopRight offsetOption:center wrapsCorner:YES];
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomLeft offsetOption:center wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomLeft offsetOption:center wrapsCorner:YES];
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomRight offsetOption:center wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomRight offsetOption:center wrapsCorner:YES];
|
||||
}
|
||||
|
||||
- (void)testCornerSpecForAllLocationsWithInnerOffset
|
||||
{
|
||||
ASCornerLayoutSpecSnapshotTestsOffsetOption inner = ASCornerLayoutSpecSnapshotTestsOffsetOptionInner;
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopLeft offsetOption:inner wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopLeft offsetOption:inner wrapsCorner:YES];
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopRight offsetOption:inner wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopRight offsetOption:inner wrapsCorner:YES];
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomLeft offsetOption:inner wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomLeft offsetOption:inner wrapsCorner:YES];
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomRight offsetOption:inner wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomRight offsetOption:inner wrapsCorner:YES];
|
||||
}
|
||||
|
||||
- (void)testCornerSpecForAllLocationsWithOuterOffset
|
||||
{
|
||||
ASCornerLayoutSpecSnapshotTestsOffsetOption outer = ASCornerLayoutSpecSnapshotTestsOffsetOptionOuter;
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopLeft offsetOption:outer wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopLeft offsetOption:outer wrapsCorner:YES];
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopRight offsetOption:outer wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopRight offsetOption:outer wrapsCorner:YES];
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomLeft offsetOption:outer wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomLeft offsetOption:outer wrapsCorner:YES];
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomRight offsetOption:outer wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomRight offsetOption:outer wrapsCorner:YES];
|
||||
}
|
||||
|
||||
- (void)testCornerSpecWithLocation:(ASCornerLayoutLocation)location
|
||||
offsetOption:(ASCornerLayoutSpecSnapshotTestsOffsetOption)offsetOption
|
||||
wrapsCorner:(BOOL)wrapsCorner
|
||||
{
|
||||
ASDisplayNode *baseNode = ASDisplayNodeWithBackgroundColor(_baseColor, _baseSize);
|
||||
ASDisplayNode *cornerNode = ASDisplayNodeWithBackgroundColor(_cornerColor, _cornerSize);
|
||||
ASDisplayNode *debugBoxNode = ASDisplayNodeWithBackgroundColor(_boxColor);
|
||||
|
||||
baseNode.style.layoutPosition = CGPointMake((_contextSize.width - _baseSize.width) / 2,
|
||||
(_contextSize.height - _baseSize.height) / 2);
|
||||
|
||||
ASCornerLayoutSpec *cornerSpec = [ASCornerLayoutSpec cornerLayoutSpecWithChild:baseNode
|
||||
corner:cornerNode
|
||||
location:location];
|
||||
|
||||
CGPoint delta = (CGPoint){ _cornerSize.width / 2, _cornerSize.height / 2 };
|
||||
cornerSpec.offset = [self offsetForOption:offsetOption location:location delta:delta];
|
||||
cornerSpec.wrapsCorner = wrapsCorner;
|
||||
|
||||
ASBackgroundLayoutSpec *backgroundSpec = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:cornerSpec
|
||||
background:debugBoxNode];
|
||||
|
||||
[self testLayoutSpec:backgroundSpec
|
||||
sizeRange:_contextSizeRange
|
||||
subnodes:@[debugBoxNode, baseNode, cornerNode]
|
||||
identifier:[self suffixWithLocation:location option:offsetOption wrapsCorner:wrapsCorner]];
|
||||
}
|
||||
|
||||
- (CGPoint)offsetForOption:(ASCornerLayoutSpecSnapshotTestsOffsetOption)option
|
||||
location:(ASCornerLayoutLocation)location
|
||||
delta:(CGPoint)delta
|
||||
{
|
||||
CGFloat x = delta.x;
|
||||
CGFloat y = delta.y;
|
||||
|
||||
switch (option) {
|
||||
|
||||
case ASCornerLayoutSpecSnapshotTestsOffsetOptionCenter:
|
||||
return CGPointZero;
|
||||
|
||||
case ASCornerLayoutSpecSnapshotTestsOffsetOptionInner:
|
||||
|
||||
switch (location) {
|
||||
case ASCornerLayoutLocationTopLeft: return (CGPoint){ x, y };
|
||||
case ASCornerLayoutLocationTopRight: return (CGPoint){ -x, y };
|
||||
case ASCornerLayoutLocationBottomLeft: return (CGPoint){ x, -y };
|
||||
case ASCornerLayoutLocationBottomRight: return (CGPoint){ -x, -y };
|
||||
}
|
||||
|
||||
case ASCornerLayoutSpecSnapshotTestsOffsetOptionOuter:
|
||||
|
||||
switch (location) {
|
||||
case ASCornerLayoutLocationTopLeft: return (CGPoint){ -x, -y };
|
||||
case ASCornerLayoutLocationTopRight: return (CGPoint){ x, -y };
|
||||
case ASCornerLayoutLocationBottomLeft: return (CGPoint){ -x, y };
|
||||
case ASCornerLayoutLocationBottomRight: return (CGPoint){ x, y };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (NSString *)suffixWithLocation:(ASCornerLayoutLocation)location
|
||||
option:(ASCornerLayoutSpecSnapshotTestsOffsetOption)option
|
||||
wrapsCorner:(BOOL)wrapsCorner
|
||||
{
|
||||
NSMutableString *desc = [NSMutableString string];
|
||||
|
||||
switch (location) {
|
||||
case ASCornerLayoutLocationTopLeft:
|
||||
[desc appendString:@"topLeft"];
|
||||
break;
|
||||
case ASCornerLayoutLocationTopRight:
|
||||
[desc appendString:@"topRight"];
|
||||
break;
|
||||
case ASCornerLayoutLocationBottomLeft:
|
||||
[desc appendString:@"bottomLeft"];
|
||||
break;
|
||||
case ASCornerLayoutLocationBottomRight:
|
||||
[desc appendString:@"bottomRight"];
|
||||
break;
|
||||
}
|
||||
|
||||
[desc appendString:@"_"];
|
||||
|
||||
switch (option) {
|
||||
case ASCornerLayoutSpecSnapshotTestsOffsetOptionCenter:
|
||||
[desc appendString:@"center"];
|
||||
break;
|
||||
case ASCornerLayoutSpecSnapshotTestsOffsetOptionInner:
|
||||
[desc appendString:@"inner"];
|
||||
break;
|
||||
case ASCornerLayoutSpecSnapshotTestsOffsetOptionOuter:
|
||||
[desc appendString:@"outer"];
|
||||
break;
|
||||
}
|
||||
|
||||
[desc appendString:@"_"];
|
||||
|
||||
if (wrapsCorner) {
|
||||
[desc appendString:@"fullSize"];
|
||||
} else {
|
||||
[desc appendString:@"childSize"];
|
||||
}
|
||||
|
||||
return desc.copy;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
//
|
||||
// ASDimensionTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/ASDimension.h>
|
||||
|
||||
#import "ASXCTExtensions.h"
|
||||
|
||||
|
||||
@interface ASDimensionTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation ASDimensionTests
|
||||
|
||||
- (void)testCreatingDimensionUnitAutos
|
||||
{
|
||||
XCTAssertNoThrow(ASDimensionMake(ASDimensionUnitAuto, 0));
|
||||
XCTAssertThrows(ASDimensionMake(ASDimensionUnitAuto, 100));
|
||||
ASXCTAssertEqualDimensions(ASDimensionAuto, ASDimensionMake(@""));
|
||||
ASXCTAssertEqualDimensions(ASDimensionAuto, ASDimensionMake(@"auto"));
|
||||
}
|
||||
|
||||
- (void)testCreatingDimensionUnitFraction
|
||||
{
|
||||
XCTAssertNoThrow(ASDimensionMake(ASDimensionUnitFraction, 0.5));
|
||||
ASXCTAssertEqualDimensions(ASDimensionMake(ASDimensionUnitFraction, 0.5), ASDimensionMake(@"50%"));
|
||||
}
|
||||
|
||||
- (void)testCreatingDimensionUnitPoints
|
||||
{
|
||||
XCTAssertNoThrow(ASDimensionMake(ASDimensionUnitPoints, 100));
|
||||
ASXCTAssertEqualDimensions(ASDimensionMake(ASDimensionUnitPoints, 100), ASDimensionMake(@"100pt"));
|
||||
}
|
||||
|
||||
- (void)testIntersectingOverlappingSizeRangesReturnsTheirIntersection
|
||||
{
|
||||
// range: |---------|
|
||||
// other: |----------|
|
||||
// result: |----|
|
||||
|
||||
ASSizeRange range = {{0,0}, {10,10}};
|
||||
ASSizeRange other = {{7,7}, {15,15}};
|
||||
ASSizeRange result = ASSizeRangeIntersect(range, other);
|
||||
ASSizeRange expected = {{7,7}, {10,10}};
|
||||
XCTAssertTrue(ASSizeRangeEqualToSizeRange(result, expected), @"Expected %@ but got %@", NSStringFromASSizeRange(expected), NSStringFromASSizeRange(result));
|
||||
}
|
||||
|
||||
- (void)testIntersectingSizeRangeWithRangeThatContainsItReturnsSameRange
|
||||
{
|
||||
// range: |-----|
|
||||
// other: |---------|
|
||||
// result: |-----|
|
||||
|
||||
ASSizeRange range = {{2,2}, {8,8}};
|
||||
ASSizeRange other = {{0,0}, {10,10}};
|
||||
ASSizeRange result = ASSizeRangeIntersect(range, other);
|
||||
ASSizeRange expected = {{2,2}, {8,8}};
|
||||
XCTAssertTrue(ASSizeRangeEqualToSizeRange(result, expected), @"Expected %@ but got %@", NSStringFromASSizeRange(expected), NSStringFromASSizeRange(result));
|
||||
}
|
||||
|
||||
- (void)testIntersectingSizeRangeWithRangeContainedWithinItReturnsContainedRange
|
||||
{
|
||||
// range: |---------|
|
||||
// other: |-----|
|
||||
// result: |-----|
|
||||
|
||||
ASSizeRange range = {{0,0}, {10,10}};
|
||||
ASSizeRange other = {{2,2}, {8,8}};
|
||||
ASSizeRange result = ASSizeRangeIntersect(range, other);
|
||||
ASSizeRange expected = {{2,2}, {8,8}};
|
||||
XCTAssertTrue(ASSizeRangeEqualToSizeRange(result, expected), @"Expected %@ but got %@", NSStringFromASSizeRange(expected), NSStringFromASSizeRange(result));
|
||||
}
|
||||
|
||||
- (void)testIntersectingSizeRangeWithNonOverlappingRangeToRightReturnsSinglePointNearestOtherRange
|
||||
{
|
||||
// range: |-----|
|
||||
// other: |---|
|
||||
// result: *
|
||||
|
||||
ASSizeRange range = {{0,0}, {5,5}};
|
||||
ASSizeRange other = {{10,10}, {15,15}};
|
||||
ASSizeRange result = ASSizeRangeIntersect(range, other);
|
||||
ASSizeRange expected = {{5,5}, {5,5}};
|
||||
XCTAssertTrue(ASSizeRangeEqualToSizeRange(result, expected), @"Expected %@ but got %@", NSStringFromASSizeRange(expected), NSStringFromASSizeRange(result));
|
||||
}
|
||||
|
||||
- (void)testIntersectingSizeRangeWithNonOverlappingRangeToLeftReturnsSinglePointNearestOtherRange
|
||||
{
|
||||
// range: |---|
|
||||
// other: |-----|
|
||||
// result: *
|
||||
|
||||
ASSizeRange range = {{10,10}, {15,15}};
|
||||
ASSizeRange other = {{0,0}, {5,5}};
|
||||
ASSizeRange result = ASSizeRangeIntersect(range, other);
|
||||
ASSizeRange expected = {{10,10}, {10,10}};
|
||||
XCTAssertTrue(ASSizeRangeEqualToSizeRange(result, expected), @"Expected %@ but got %@", NSStringFromASSizeRange(expected), NSStringFromASSizeRange(result));
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
//
|
||||
// ASDispatchTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/ASDispatch.h>
|
||||
|
||||
@interface ASDispatchTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASDispatchTests
|
||||
|
||||
- (void)testDispatchApply
|
||||
{
|
||||
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||||
NSInteger expectedThreadCount = [NSProcessInfo processInfo].activeProcessorCount * 2;
|
||||
NSLock *lock = [NSLock new];
|
||||
NSMutableSet *threads = [NSMutableSet set];
|
||||
NSMutableIndexSet *indices = [NSMutableIndexSet indexSet];
|
||||
|
||||
size_t const iterations = 1E5;
|
||||
ASDispatchApply(iterations, q, 0, ^(size_t i) {
|
||||
[lock lock];
|
||||
[threads addObject:[NSThread currentThread]];
|
||||
XCTAssertFalse([indices containsIndex:i]);
|
||||
[indices addIndex:i];
|
||||
[lock unlock];
|
||||
});
|
||||
XCTAssertLessThanOrEqual(threads.count, expectedThreadCount);
|
||||
XCTAssertEqualObjects(indices, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, iterations)]);
|
||||
}
|
||||
|
||||
- (void)testDispatchAsync
|
||||
{
|
||||
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||||
NSInteger expectedThreadCount = [NSProcessInfo processInfo].activeProcessorCount * 2;
|
||||
NSLock *lock = [NSLock new];
|
||||
NSMutableSet *threads = [NSMutableSet set];
|
||||
NSMutableIndexSet *indices = [NSMutableIndexSet indexSet];
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Executed all blocks"];
|
||||
|
||||
size_t const iterations = 1E5;
|
||||
ASDispatchAsync(iterations, q, 0, ^(size_t i) {
|
||||
[lock lock];
|
||||
[threads addObject:[NSThread currentThread]];
|
||||
XCTAssertFalse([indices containsIndex:i]);
|
||||
[indices addIndex:i];
|
||||
if (indices.count == iterations) {
|
||||
[expectation fulfill];
|
||||
}
|
||||
[lock unlock];
|
||||
});
|
||||
[self waitForExpectationsWithTimeout:10 handler:nil];
|
||||
XCTAssertLessThanOrEqual(threads.count, expectedThreadCount);
|
||||
XCTAssertEqualObjects(indices, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, iterations)]);
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,598 +0,0 @@
|
|||
//
|
||||
// ASDisplayLayerTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
#import "ASDisplayNodeTestsHelper.h"
|
||||
|
||||
static UIImage *bogusImage() {
|
||||
static UIImage *bogusImage = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
|
||||
UIGraphicsBeginImageContext(CGSizeMake(10, 10));
|
||||
|
||||
bogusImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
});
|
||||
|
||||
return bogusImage;
|
||||
}
|
||||
|
||||
@interface _ASDisplayLayerTestContainerLayer : CALayer
|
||||
@property (nonatomic, readonly) NSUInteger didCompleteTransactionCount;
|
||||
@end
|
||||
|
||||
@implementation _ASDisplayLayerTestContainerLayer
|
||||
|
||||
- (void)asyncdisplaykit_asyncTransactionContainerDidCompleteTransaction:(_ASAsyncTransaction *)transaction
|
||||
{
|
||||
_didCompleteTransactionCount++;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ASDisplayNode (HackForTests)
|
||||
- (id)initWithViewClass:(Class)viewClass;
|
||||
- (id)initWithLayerClass:(Class)layerClass;
|
||||
@end
|
||||
|
||||
|
||||
@interface _ASDisplayLayerTestLayer : _ASDisplayLayer
|
||||
{
|
||||
BOOL _isInCancelAsyncDisplay;
|
||||
BOOL _isInDisplay;
|
||||
}
|
||||
@property (nonatomic, readonly) NSUInteger displayCount;
|
||||
@property (nonatomic, readonly) NSUInteger drawInContextCount;
|
||||
@property (nonatomic, readonly) NSUInteger setContentsAsyncCount;
|
||||
@property (nonatomic, readonly) NSUInteger setContentsSyncCount;
|
||||
@property (nonatomic, copy, readonly) NSString *setContentsCounts;
|
||||
- (BOOL)checkSetContentsCountsWithSyncCount:(NSUInteger)syncCount asyncCount:(NSUInteger)asyncCount;
|
||||
@end
|
||||
|
||||
@implementation _ASDisplayLayerTestLayer
|
||||
|
||||
- (NSString *)setContentsCounts
|
||||
{
|
||||
return [NSString stringWithFormat:@"syncCount:%tu, asyncCount:%tu", _setContentsSyncCount, _setContentsAsyncCount];
|
||||
}
|
||||
|
||||
- (BOOL)checkSetContentsCountsWithSyncCount:(NSUInteger)syncCount asyncCount:(NSUInteger)asyncCount
|
||||
{
|
||||
return ((syncCount == _setContentsSyncCount) &&
|
||||
(asyncCount == _setContentsAsyncCount));
|
||||
}
|
||||
|
||||
- (void)setContents:(id)contents
|
||||
{
|
||||
[super setContents:contents];
|
||||
|
||||
if (self.displaysAsynchronously) {
|
||||
if (_isInDisplay) {
|
||||
[NSException raise:NSInvalidArgumentException format:@"There is no placeholder logic in _ASDisplayLayer, unknown caller for setContents:"];
|
||||
} else if (!_isInCancelAsyncDisplay) {
|
||||
_setContentsAsyncCount++;
|
||||
}
|
||||
} else {
|
||||
_setContentsSyncCount++;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)display
|
||||
{
|
||||
_isInDisplay = YES;
|
||||
[super display];
|
||||
_isInDisplay = NO;
|
||||
_displayCount++;
|
||||
}
|
||||
|
||||
- (void)cancelAsyncDisplay
|
||||
{
|
||||
_isInCancelAsyncDisplay = YES;
|
||||
[super cancelAsyncDisplay];
|
||||
_isInCancelAsyncDisplay = NO;
|
||||
}
|
||||
|
||||
// This should never get called. This just records if it is.
|
||||
- (void)drawInContext:(CGContextRef)context
|
||||
{
|
||||
[super drawInContext:context];
|
||||
_drawInContextCount++;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
typedef NS_ENUM(NSUInteger, _ASDisplayLayerTestDelegateMode)
|
||||
{
|
||||
_ASDisplayLayerTestDelegateModeNone = 0,
|
||||
_ASDisplayLayerTestDelegateModeDrawParameters = 1 << 0,
|
||||
_ASDisplayLayerTestDelegateModeWillDisplay = 1 << 1,
|
||||
_ASDisplayLayerTestDelegateModeDidDisplay = 1 << 2,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, _ASDisplayLayerTestDelegateClassModes) {
|
||||
_ASDisplayLayerTestDelegateClassModeNone = 0,
|
||||
_ASDisplayLayerTestDelegateClassModeDisplay = 1 << 0,
|
||||
_ASDisplayLayerTestDelegateClassModeDrawInContext = 1 << 1,
|
||||
};
|
||||
|
||||
@interface _ASDisplayLayerTestDelegate : ASDisplayNode <_ASDisplayLayerDelegate>
|
||||
|
||||
@property (nonatomic) NSUInteger didDisplayCount;
|
||||
@property (nonatomic) NSUInteger drawParametersCount;
|
||||
@property (nonatomic) NSUInteger willDisplayCount;
|
||||
|
||||
// for _ASDisplayLayerTestDelegateModeClassDisplay
|
||||
@property (nonatomic) NSUInteger displayCount;
|
||||
@property (nonatomic) UIImage *(^displayLayerBlock)(void);
|
||||
|
||||
// for _ASDisplayLayerTestDelegateModeClassDrawInContext
|
||||
@property (nonatomic) NSUInteger drawRectCount;
|
||||
|
||||
@end
|
||||
|
||||
@implementation _ASDisplayLayerTestDelegate {
|
||||
_ASDisplayLayerTestDelegateMode _modes;
|
||||
}
|
||||
|
||||
static _ASDisplayLayerTestDelegateClassModes _class_modes;
|
||||
|
||||
+ (void)setClassModes:(_ASDisplayLayerTestDelegateClassModes)classModes
|
||||
{
|
||||
_class_modes = classModes;
|
||||
}
|
||||
|
||||
- (id)initWithModes:(_ASDisplayLayerTestDelegateMode)modes
|
||||
{
|
||||
_modes = modes;
|
||||
|
||||
if (!(self = [super initWithLayerClass:[_ASDisplayLayerTestLayer class]]))
|
||||
return nil;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)didDisplayAsyncLayer:(_ASDisplayLayer *)layer
|
||||
{
|
||||
_didDisplayCount++;
|
||||
}
|
||||
|
||||
- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer
|
||||
{
|
||||
_drawParametersCount++;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)willDisplayAsyncLayer:(_ASDisplayLayer *)layer
|
||||
{
|
||||
_willDisplayCount++;
|
||||
}
|
||||
|
||||
- (BOOL)respondsToSelector:(SEL)selector
|
||||
{
|
||||
if (sel_isEqual(selector, @selector(didDisplayAsyncLayer:))) {
|
||||
return (_modes & _ASDisplayLayerTestDelegateModeDidDisplay);
|
||||
} else if (sel_isEqual(selector, @selector(drawParametersForAsyncLayer:))) {
|
||||
return (_modes & _ASDisplayLayerTestDelegateModeDrawParameters);
|
||||
} else if (sel_isEqual(selector, @selector(willDisplayAsyncLayer:))) {
|
||||
return (_modes & _ASDisplayLayerTestDelegateModeWillDisplay);
|
||||
} else {
|
||||
return [super respondsToSelector:selector];
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)respondsToSelector:(SEL)selector
|
||||
{
|
||||
if (sel_isEqual(selector, @selector(displayWithParameters:isCancelled:))) {
|
||||
return _class_modes & _ASDisplayLayerTestDelegateClassModeDisplay;
|
||||
} else if (sel_isEqual(selector, @selector(drawRect:withParameters:isCancelled:isRasterizing:))) {
|
||||
return _class_modes & _ASDisplayLayerTestDelegateClassModeDrawInContext;
|
||||
} else {
|
||||
return [super respondsToSelector:selector];
|
||||
}
|
||||
}
|
||||
|
||||
// DANGER: Don't use the delegate as the parameters in real code; this is not thread-safe and just for accounting in unit tests!
|
||||
+ (UIImage *)displayWithParameters:(_ASDisplayLayerTestDelegate *)delegate isCancelled:(NS_NOESCAPE asdisplaynode_iscancelled_block_t)sentinelBlock
|
||||
{
|
||||
UIImage *contents = bogusImage();
|
||||
if (delegate->_displayLayerBlock != NULL) {
|
||||
contents = delegate->_displayLayerBlock();
|
||||
}
|
||||
delegate->_displayCount++;
|
||||
return contents;
|
||||
}
|
||||
|
||||
// DANGER: Don't use the delegate as the parameters in real code; this is not thread-safe and just for accounting in unit tests!
|
||||
+ (void)drawRect:(CGRect)bounds withParameters:(_ASDisplayLayerTestDelegate *)delegate isCancelled:(NS_NOESCAPE asdisplaynode_iscancelled_block_t)sentinelBlock isRasterizing:(BOOL)isRasterizing
|
||||
{
|
||||
__atomic_add_fetch(&delegate->_drawRectCount, 1, __ATOMIC_SEQ_CST);
|
||||
}
|
||||
|
||||
- (NSUInteger)drawRectCount
|
||||
{
|
||||
return(__atomic_load_n(&_drawRectCount, __ATOMIC_SEQ_CST));
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface _ASDisplayLayerTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation _ASDisplayLayerTests
|
||||
|
||||
- (void)setUp {
|
||||
[super setUp];
|
||||
// Force bogusImage() to create+cache its image. This impacts any time-sensitive tests which call the method from
|
||||
// within the timed portion of the test. It seems that, in rare cases, this image creation can take a bit too long,
|
||||
// causing a test failure.
|
||||
bogusImage();
|
||||
}
|
||||
|
||||
// since we're not running in an application, we need to force this display on layer the hierarchy
|
||||
- (void)displayLayerRecursively:(CALayer *)layer
|
||||
{
|
||||
if (layer.needsDisplay) {
|
||||
[layer displayIfNeeded];
|
||||
}
|
||||
for (CALayer *sublayer in layer.sublayers) {
|
||||
[self displayLayerRecursively:sublayer];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)waitForDisplayQueue
|
||||
{
|
||||
// make sure we don't lock up the tests indefinitely; fail after 1 sec by using an async barrier
|
||||
__block BOOL didHitBarrier = NO;
|
||||
dispatch_barrier_async([_ASDisplayLayer displayQueue], ^{
|
||||
__atomic_store_n(&didHitBarrier, YES, __ATOMIC_SEQ_CST);
|
||||
});
|
||||
XCTAssertTrue(ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{ return __atomic_load_n(&didHitBarrier, __ATOMIC_SEQ_CST); }));
|
||||
}
|
||||
|
||||
- (void)waitForLayer:(_ASDisplayLayerTestLayer *)layer asyncDisplayCount:(NSUInteger)count
|
||||
{
|
||||
// make sure we don't lock up the tests indefinitely; fail after 1 sec of waiting for the setContents async count to increment
|
||||
// NOTE: the layer sets its contents async back on the main queue, so we need to wait for main
|
||||
XCTAssertTrue(ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{
|
||||
return (layer.setContentsAsyncCount == count);
|
||||
}));
|
||||
}
|
||||
|
||||
- (void)waitForAsyncDelegate:(_ASDisplayLayerTestDelegate *)asyncDelegate
|
||||
{
|
||||
XCTAssertTrue(ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{
|
||||
return (asyncDelegate.didDisplayCount == 1);
|
||||
}));
|
||||
}
|
||||
|
||||
- (void)checkDelegateDisplay:(BOOL)displaysAsynchronously
|
||||
{
|
||||
[_ASDisplayLayerTestDelegate setClassModes:_ASDisplayLayerTestDelegateClassModeDisplay];
|
||||
_ASDisplayLayerTestDelegate *asyncDelegate = [[_ASDisplayLayerTestDelegate alloc] initWithModes:_ASDisplayLayerTestDelegateMode(_ASDisplayLayerTestDelegateModeDidDisplay | _ASDisplayLayerTestDelegateModeDrawParameters)];
|
||||
|
||||
_ASDisplayLayerTestLayer *layer = (_ASDisplayLayerTestLayer *)asyncDelegate.layer;
|
||||
layer.displaysAsynchronously = displaysAsynchronously;
|
||||
|
||||
if (displaysAsynchronously) {
|
||||
dispatch_suspend([_ASDisplayLayer displayQueue]);
|
||||
}
|
||||
layer.frame = CGRectMake(0.0, 0.0, 100.0, 100.0);
|
||||
[layer setNeedsDisplay];
|
||||
[layer displayIfNeeded];
|
||||
|
||||
if (displaysAsynchronously) {
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
XCTAssertEqual(layer.displayCount, 1u);
|
||||
XCTAssertEqual(layer.drawInContextCount, 0u);
|
||||
dispatch_resume([_ASDisplayLayer displayQueue]);
|
||||
[self waitForDisplayQueue];
|
||||
[self waitForAsyncDelegate:asyncDelegate];
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:1], @"%@", layer.setContentsCounts);
|
||||
} else {
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:1 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
}
|
||||
|
||||
XCTAssertFalse(layer.needsDisplay);
|
||||
XCTAssertEqual(layer.displayCount, 1u);
|
||||
XCTAssertEqual(layer.drawInContextCount, 0u);
|
||||
XCTAssertEqual(asyncDelegate.didDisplayCount, 1u);
|
||||
XCTAssertEqual(asyncDelegate.displayCount, 1u);
|
||||
}
|
||||
|
||||
- (void)testDelegateDisplaySync
|
||||
{
|
||||
[self checkDelegateDisplay:NO];
|
||||
}
|
||||
|
||||
- (void)testDelegateDisplayAsync
|
||||
{
|
||||
[self checkDelegateDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)checkDelegateDrawInContext:(BOOL)displaysAsynchronously
|
||||
{
|
||||
[_ASDisplayLayerTestDelegate setClassModes:_ASDisplayLayerTestDelegateClassModeDrawInContext];
|
||||
_ASDisplayLayerTestDelegate *asyncDelegate = [[_ASDisplayLayerTestDelegate alloc] initWithModes:_ASDisplayLayerTestDelegateMode(_ASDisplayLayerTestDelegateModeDidDisplay | _ASDisplayLayerTestDelegateModeDrawParameters)];
|
||||
|
||||
_ASDisplayLayerTestLayer *layer = (_ASDisplayLayerTestLayer *)asyncDelegate.layer;
|
||||
layer.displaysAsynchronously = displaysAsynchronously;
|
||||
|
||||
if (displaysAsynchronously) {
|
||||
dispatch_suspend([_ASDisplayLayer displayQueue]);
|
||||
}
|
||||
layer.frame = CGRectMake(0.0, 0.0, 100.0, 100.0);
|
||||
[layer setNeedsDisplay];
|
||||
[layer displayIfNeeded];
|
||||
|
||||
if (displaysAsynchronously) {
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
XCTAssertEqual(layer.displayCount, 1u);
|
||||
XCTAssertEqual(layer.drawInContextCount, 0u);
|
||||
XCTAssertEqual(asyncDelegate.drawRectCount, 0u);
|
||||
dispatch_resume([_ASDisplayLayer displayQueue]);
|
||||
[self waitForLayer:layer asyncDisplayCount:1];
|
||||
[self waitForAsyncDelegate:asyncDelegate];
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:1], @"%@", layer.setContentsCounts);
|
||||
} else {
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:1 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
}
|
||||
|
||||
XCTAssertFalse(layer.needsDisplay);
|
||||
XCTAssertEqual(layer.displayCount, 1u);
|
||||
XCTAssertEqual(layer.drawInContextCount, 0u);
|
||||
XCTAssertEqual(asyncDelegate.didDisplayCount, 1u);
|
||||
XCTAssertEqual(asyncDelegate.displayCount, 0u);
|
||||
XCTAssertEqual(asyncDelegate.drawParametersCount, 1u);
|
||||
XCTAssertEqual(asyncDelegate.drawRectCount, 1u);
|
||||
}
|
||||
|
||||
- (void)testDelegateDrawInContextSync
|
||||
{
|
||||
[self checkDelegateDrawInContext:NO];
|
||||
}
|
||||
|
||||
- (void)testDelegateDrawInContextAsync
|
||||
{
|
||||
[self checkDelegateDrawInContext:YES];
|
||||
}
|
||||
|
||||
- (void)checkDelegateDisplayAndDrawInContext:(BOOL)displaysAsynchronously
|
||||
{
|
||||
[_ASDisplayLayerTestDelegate setClassModes:_ASDisplayLayerTestDelegateClassModes(_ASDisplayLayerTestDelegateClassModeDisplay | _ASDisplayLayerTestDelegateClassModeDrawInContext)];
|
||||
_ASDisplayLayerTestDelegate *asyncDelegate = [[_ASDisplayLayerTestDelegate alloc] initWithModes:_ASDisplayLayerTestDelegateMode(_ASDisplayLayerTestDelegateModeDidDisplay | _ASDisplayLayerTestDelegateModeDrawParameters)];
|
||||
|
||||
_ASDisplayLayerTestLayer *layer = (_ASDisplayLayerTestLayer *)asyncDelegate.layer;
|
||||
layer.displaysAsynchronously = displaysAsynchronously;
|
||||
|
||||
if (displaysAsynchronously) {
|
||||
dispatch_suspend([_ASDisplayLayer displayQueue]);
|
||||
}
|
||||
layer.frame = CGRectMake(0.0, 0.0, 100.0, 100.0);
|
||||
[layer setNeedsDisplay];
|
||||
[layer displayIfNeeded];
|
||||
|
||||
if (displaysAsynchronously) {
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
XCTAssertEqual(layer.displayCount, 1u);
|
||||
XCTAssertEqual(asyncDelegate.drawParametersCount, 1u);
|
||||
XCTAssertEqual(asyncDelegate.drawRectCount, 0u);
|
||||
dispatch_resume([_ASDisplayLayer displayQueue]);
|
||||
[self waitForDisplayQueue];
|
||||
[self waitForAsyncDelegate:asyncDelegate];
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:1], @"%@", layer.setContentsCounts);
|
||||
} else {
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:1 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
}
|
||||
|
||||
XCTAssertFalse(layer.needsDisplay);
|
||||
XCTAssertEqual(layer.displayCount, 1u);
|
||||
XCTAssertEqual(layer.drawInContextCount, 0u);
|
||||
XCTAssertEqual(asyncDelegate.didDisplayCount, 1u);
|
||||
XCTAssertEqual(asyncDelegate.displayCount, 1u);
|
||||
XCTAssertEqual(asyncDelegate.drawParametersCount, 1u);
|
||||
XCTAssertEqual(asyncDelegate.drawRectCount, 0u);
|
||||
}
|
||||
|
||||
- (void)testDelegateDisplayAndDrawInContextSync
|
||||
{
|
||||
[self checkDelegateDisplayAndDrawInContext:NO];
|
||||
}
|
||||
|
||||
- (void)testDelegateDisplayAndDrawInContextAsync
|
||||
{
|
||||
[self checkDelegateDisplayAndDrawInContext:YES];
|
||||
}
|
||||
|
||||
- (void)testCancelAsyncDisplay
|
||||
{
|
||||
[_ASDisplayLayerTestDelegate setClassModes:_ASDisplayLayerTestDelegateClassModeDisplay];
|
||||
_ASDisplayLayerTestDelegate *asyncDelegate = [[_ASDisplayLayerTestDelegate alloc] initWithModes:_ASDisplayLayerTestDelegateModeDidDisplay];
|
||||
_ASDisplayLayerTestLayer *layer = (_ASDisplayLayerTestLayer *)asyncDelegate.layer;
|
||||
|
||||
dispatch_suspend([_ASDisplayLayer displayQueue]);
|
||||
layer.frame = CGRectMake(0.0, 0.0, 100.0, 100.0);
|
||||
[layer setNeedsDisplay];
|
||||
XCTAssertTrue(layer.needsDisplay);
|
||||
[layer displayIfNeeded];
|
||||
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
XCTAssertFalse(layer.needsDisplay);
|
||||
XCTAssertEqual(layer.displayCount, 1u);
|
||||
XCTAssertEqual(layer.drawInContextCount, 0u);
|
||||
|
||||
[layer cancelAsyncDisplay];
|
||||
|
||||
dispatch_resume([_ASDisplayLayer displayQueue]);
|
||||
[self waitForDisplayQueue];
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
XCTAssertEqual(layer.displayCount, 1u);
|
||||
XCTAssertEqual(layer.drawInContextCount, 0u);
|
||||
XCTAssertEqual(asyncDelegate.didDisplayCount, 0u);
|
||||
XCTAssertEqual(asyncDelegate.displayCount, 0u);
|
||||
XCTAssertEqual(asyncDelegate.drawParametersCount, 0u);
|
||||
}
|
||||
|
||||
- (void)DISABLED_testTransaction
|
||||
{
|
||||
auto delegateModes = _ASDisplayLayerTestDelegateMode(_ASDisplayLayerTestDelegateModeDidDisplay | _ASDisplayLayerTestDelegateModeDrawParameters);
|
||||
[_ASDisplayLayerTestDelegate setClassModes:_ASDisplayLayerTestDelegateClassModeDisplay];
|
||||
|
||||
// Setup
|
||||
_ASDisplayLayerTestContainerLayer *containerLayer = [[_ASDisplayLayerTestContainerLayer alloc] init];
|
||||
containerLayer.asyncdisplaykit_asyncTransactionContainer = YES;
|
||||
containerLayer.frame = CGRectMake(0.0, 0.0, 100.0, 100.0);
|
||||
|
||||
_ASDisplayLayerTestDelegate *layer1Delegate = [[_ASDisplayLayerTestDelegate alloc] initWithModes:delegateModes];
|
||||
_ASDisplayLayerTestLayer *layer1 = (_ASDisplayLayerTestLayer *)layer1Delegate.layer;
|
||||
layer1.displaysAsynchronously = YES;
|
||||
|
||||
dispatch_semaphore_t displayAsyncLayer1Sema = dispatch_semaphore_create(0);
|
||||
layer1Delegate.displayLayerBlock = ^UIImage *{
|
||||
dispatch_semaphore_wait(displayAsyncLayer1Sema, DISPATCH_TIME_FOREVER);
|
||||
return bogusImage();
|
||||
};
|
||||
layer1.backgroundColor = [UIColor blackColor].CGColor;
|
||||
layer1.frame = CGRectMake(0.0, 0.0, 333.0, 123.0);
|
||||
[containerLayer addSublayer:layer1];
|
||||
|
||||
_ASDisplayLayerTestDelegate *layer2Delegate = [[_ASDisplayLayerTestDelegate alloc] initWithModes:delegateModes];
|
||||
_ASDisplayLayerTestLayer *layer2 = (_ASDisplayLayerTestLayer *)layer2Delegate.layer;
|
||||
layer2.displaysAsynchronously = YES;
|
||||
layer2.backgroundColor = [UIColor blackColor].CGColor;
|
||||
layer2.frame = CGRectMake(0.0, 50.0, 97.0, 50.0);
|
||||
[containerLayer addSublayer:layer2];
|
||||
|
||||
dispatch_suspend([_ASDisplayLayer displayQueue]);
|
||||
|
||||
// display below if needed
|
||||
[layer1 setNeedsDisplay];
|
||||
[layer2 setNeedsDisplay];
|
||||
[containerLayer setNeedsDisplay];
|
||||
[self displayLayerRecursively:containerLayer];
|
||||
|
||||
// check state before running displayQueue
|
||||
XCTAssertTrue([layer1 checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer1.setContentsCounts);
|
||||
XCTAssertEqual(layer1.displayCount, 1u);
|
||||
XCTAssertEqual(layer1Delegate.displayCount, 0u);
|
||||
XCTAssertTrue([layer2 checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer2.setContentsCounts);
|
||||
XCTAssertEqual(layer2.displayCount, 1u);
|
||||
XCTAssertEqual(layer1Delegate.displayCount, 0u);
|
||||
XCTAssertEqual(containerLayer.didCompleteTransactionCount, 0u);
|
||||
|
||||
// run displayQueue until async display for layer2 has been run
|
||||
dispatch_resume([_ASDisplayLayer displayQueue]);
|
||||
XCTAssertTrue(ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{
|
||||
return (layer2Delegate.displayCount == 1);
|
||||
}));
|
||||
|
||||
// check layer1 has not had async display run
|
||||
XCTAssertTrue([layer1 checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer1.setContentsCounts);
|
||||
XCTAssertEqual(layer1.displayCount, 1u);
|
||||
XCTAssertEqual(layer1Delegate.displayCount, 0u);
|
||||
// check layer2 has had async display run
|
||||
XCTAssertTrue([layer2 checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer2.setContentsCounts);
|
||||
XCTAssertEqual(layer2.displayCount, 1u);
|
||||
XCTAssertEqual(layer2Delegate.displayCount, 1u);
|
||||
XCTAssertEqual(containerLayer.didCompleteTransactionCount, 0u);
|
||||
|
||||
|
||||
// allow layer1 to complete display
|
||||
dispatch_semaphore_signal(displayAsyncLayer1Sema);
|
||||
[self waitForLayer:layer1 asyncDisplayCount:1];
|
||||
|
||||
// check that both layers have completed display
|
||||
XCTAssertTrue([layer1 checkSetContentsCountsWithSyncCount:0 asyncCount:1], @"%@", layer1.setContentsCounts);
|
||||
XCTAssertEqual(layer1.displayCount, 1u);
|
||||
XCTAssertEqual(layer1Delegate.displayCount, 1u);
|
||||
XCTAssertTrue([layer2 checkSetContentsCountsWithSyncCount:0 asyncCount:1], @"%@", layer2.setContentsCounts);
|
||||
XCTAssertEqual(layer2.displayCount, 1u);
|
||||
XCTAssertEqual(layer2Delegate.displayCount, 1u);
|
||||
|
||||
XCTAssertTrue(ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{
|
||||
return (containerLayer.didCompleteTransactionCount == 1);
|
||||
}));
|
||||
}
|
||||
|
||||
- (void)checkSuspendResume:(BOOL)displaysAsynchronously
|
||||
{
|
||||
[_ASDisplayLayerTestDelegate setClassModes:_ASDisplayLayerTestDelegateClassModeDrawInContext];
|
||||
_ASDisplayLayerTestDelegate *asyncDelegate = [[_ASDisplayLayerTestDelegate alloc] initWithModes:_ASDisplayLayerTestDelegateMode(_ASDisplayLayerTestDelegateModeDidDisplay | _ASDisplayLayerTestDelegateModeDrawParameters)];
|
||||
|
||||
_ASDisplayLayerTestLayer *layer = (_ASDisplayLayerTestLayer *)asyncDelegate.layer;
|
||||
layer.displaysAsynchronously = displaysAsynchronously;
|
||||
layer.frame = CGRectMake(0.0, 0.0, 100.0, 100.0);
|
||||
|
||||
if (displaysAsynchronously) {
|
||||
dispatch_suspend([_ASDisplayLayer displayQueue]);
|
||||
}
|
||||
|
||||
// Layer shouldn't display because display is suspended
|
||||
layer.displaySuspended = YES;
|
||||
[layer setNeedsDisplay];
|
||||
[layer displayIfNeeded];
|
||||
XCTAssertEqual(layer.displayCount, 0u, @"Should not have displayed because display is suspended, thus -setNeedsDisplay is a no-op");
|
||||
XCTAssertFalse(layer.needsDisplay, @"Should not need display");
|
||||
if (displaysAsynchronously) {
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
dispatch_resume([_ASDisplayLayer displayQueue]);
|
||||
[self waitForDisplayQueue];
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
} else {
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
}
|
||||
XCTAssertFalse(layer.needsDisplay);
|
||||
XCTAssertEqual(layer.drawInContextCount, 0u);
|
||||
XCTAssertEqual(asyncDelegate.drawRectCount, 0u);
|
||||
|
||||
// Layer should display because display is resumed
|
||||
if (displaysAsynchronously) {
|
||||
dispatch_suspend([_ASDisplayLayer displayQueue]);
|
||||
}
|
||||
layer.displaySuspended = NO;
|
||||
XCTAssertTrue(layer.needsDisplay);
|
||||
[layer displayIfNeeded];
|
||||
XCTAssertEqual(layer.displayCount, 1u);
|
||||
if (displaysAsynchronously) {
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
XCTAssertEqual(layer.drawInContextCount, 0u);
|
||||
XCTAssertEqual(asyncDelegate.drawRectCount, 0u);
|
||||
dispatch_resume([_ASDisplayLayer displayQueue]);
|
||||
[self waitForLayer:layer asyncDisplayCount:1];
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:1], @"%@", layer.setContentsCounts);
|
||||
} else {
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:1 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
}
|
||||
XCTAssertEqual(layer.drawInContextCount, 0u);
|
||||
XCTAssertEqual(asyncDelegate.drawParametersCount, 1u);
|
||||
XCTAssertEqual(asyncDelegate.drawRectCount, 1u);
|
||||
XCTAssertFalse(layer.needsDisplay);
|
||||
}
|
||||
|
||||
- (void)testSuspendResumeAsync
|
||||
{
|
||||
[self checkSuspendResume:YES];
|
||||
}
|
||||
|
||||
- (void)testSuspendResumeSync
|
||||
{
|
||||
[self checkSuspendResume:NO];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,417 +0,0 @@
|
|||
//
|
||||
// ASDisplayNodeAppearanceTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/_ASDisplayView.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
#import <AsyncDisplayKit/UIView+ASConvenience.h>
|
||||
|
||||
// helper functions
|
||||
IMP class_replaceMethodWithBlock(Class theClass, SEL originalSelector, id block);
|
||||
IMP class_replaceMethodWithBlock(Class theClass, SEL originalSelector, id block)
|
||||
{
|
||||
IMP newImplementation = imp_implementationWithBlock(block);
|
||||
Method method = class_getInstanceMethod(theClass, originalSelector);
|
||||
return class_replaceMethod(theClass, originalSelector, newImplementation, method_getTypeEncoding(method));
|
||||
}
|
||||
|
||||
static dispatch_block_t modifyMethodByAddingPrologueBlockAndReturnCleanupBlock(Class theClass, SEL originalSelector, void (^block)(id))
|
||||
{
|
||||
__block IMP originalImp = NULL;
|
||||
void (^blockActualSwizzle)(id) = ^(id swizzedSelf){
|
||||
block(swizzedSelf);
|
||||
((void(*)(id, SEL))originalImp)(swizzedSelf, originalSelector);
|
||||
};
|
||||
originalImp = class_replaceMethodWithBlock(theClass, originalSelector, blockActualSwizzle);
|
||||
void (^cleanupBlock)(void) = ^{
|
||||
// restore original method
|
||||
Method method = class_getInstanceMethod(theClass, originalSelector);
|
||||
class_replaceMethod(theClass, originalSelector, originalImp, method_getTypeEncoding(method));
|
||||
};
|
||||
return cleanupBlock;
|
||||
};
|
||||
|
||||
@interface ASDisplayNode (PrivateStuffSoWeDontPullInCPPInternalH)
|
||||
- (BOOL)__visibilityNotificationsDisabled;
|
||||
- (BOOL)__selfOrParentHasVisibilityNotificationsDisabled;
|
||||
- (id)initWithViewClass:(Class)viewClass;
|
||||
- (id)initWithLayerClass:(Class)layerClass;
|
||||
@end
|
||||
|
||||
@interface ASDisplayNodeAppearanceTests : XCTestCase
|
||||
@end
|
||||
|
||||
// Conveniences for making nodes named a certain way
|
||||
#define DeclareNodeNamed(n) ASDisplayNode *n = [[ASDisplayNode alloc] init]; n.debugName = @#n
|
||||
#define DeclareViewNamed(v) \
|
||||
ASDisplayNode *node_##v = [[ASDisplayNode alloc] init]; \
|
||||
node_##v.debugName = @#v; \
|
||||
UIView *v = node_##v.view;
|
||||
|
||||
@implementation ASDisplayNodeAppearanceTests
|
||||
{
|
||||
_ASDisplayView *_view;
|
||||
|
||||
NSMutableArray *_swizzleCleanupBlocks;
|
||||
|
||||
NSCountedSet *_willEnterHierarchyCounts;
|
||||
NSCountedSet *_didExitHierarchyCounts;
|
||||
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
|
||||
_swizzleCleanupBlocks = [[NSMutableArray alloc] init];
|
||||
|
||||
// Using this instead of mocks. Count # of times method called
|
||||
_willEnterHierarchyCounts = [[NSCountedSet alloc] init];
|
||||
_didExitHierarchyCounts = [[NSCountedSet alloc] init];
|
||||
|
||||
dispatch_block_t cleanupBlock = modifyMethodByAddingPrologueBlockAndReturnCleanupBlock([ASDisplayNode class], @selector(willEnterHierarchy), ^(id blockSelf){
|
||||
[_willEnterHierarchyCounts addObject:blockSelf];
|
||||
});
|
||||
[_swizzleCleanupBlocks addObject:cleanupBlock];
|
||||
cleanupBlock = modifyMethodByAddingPrologueBlockAndReturnCleanupBlock([ASDisplayNode class], @selector(didExitHierarchy), ^(id blockSelf){
|
||||
[_didExitHierarchyCounts addObject:blockSelf];
|
||||
});
|
||||
[_swizzleCleanupBlocks addObject:cleanupBlock];
|
||||
}
|
||||
|
||||
- (void)tearDown
|
||||
{
|
||||
[super tearDown];
|
||||
|
||||
for(dispatch_block_t cleanupBlock in _swizzleCleanupBlocks) {
|
||||
cleanupBlock();
|
||||
}
|
||||
_swizzleCleanupBlocks = nil;
|
||||
_willEnterHierarchyCounts = nil;
|
||||
_didExitHierarchyCounts = nil;
|
||||
}
|
||||
|
||||
- (void)testAppearanceMethodsCalledWithRootNodeInWindowLayer
|
||||
{
|
||||
[self checkAppearanceMethodsCalledWithRootNodeInWindowLayerBacked:YES];
|
||||
}
|
||||
|
||||
- (void)testAppearanceMethodsCalledWithRootNodeInWindowView
|
||||
{
|
||||
[self checkAppearanceMethodsCalledWithRootNodeInWindowLayerBacked:NO];
|
||||
}
|
||||
|
||||
- (void)checkAppearanceMethodsCalledWithRootNodeInWindowLayerBacked:(BOOL)isLayerBacked
|
||||
{
|
||||
// ASDisplayNode visibility does not change if modifying a hierarchy that is not in a window. So create one and add the superview to it.
|
||||
UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectZero];
|
||||
|
||||
DeclareNodeNamed(n);
|
||||
DeclareViewNamed(superview);
|
||||
|
||||
n.layerBacked = isLayerBacked;
|
||||
|
||||
if (isLayerBacked) {
|
||||
[superview.layer addSublayer:n.layer];
|
||||
} else {
|
||||
[superview addSubview:n.view];
|
||||
}
|
||||
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:n], 0u, @"willEnterHierarchy erroneously called");
|
||||
XCTAssertEqual([_didExitHierarchyCounts countForObject:n], 0u, @"didExitHierarchy erroneously called");
|
||||
|
||||
[window addSubview:superview];
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:n], 1u, @"willEnterHierarchy not called when node's view added to hierarchy");
|
||||
XCTAssertEqual([_didExitHierarchyCounts countForObject:n], 0u, @"didExitHierarchy erroneously called");
|
||||
|
||||
XCTAssertTrue(n.inHierarchy, @"Node should be visible");
|
||||
|
||||
if (isLayerBacked) {
|
||||
[n.layer removeFromSuperlayer];
|
||||
} else {
|
||||
[n.view removeFromSuperview];
|
||||
}
|
||||
|
||||
XCTAssertFalse(n.inHierarchy, @"Node should be not visible");
|
||||
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:n], 1u, @"willEnterHierarchy not called when node's view added to hierarchy");
|
||||
XCTAssertEqual([_didExitHierarchyCounts countForObject:n], 1u, @"didExitHierarchy erroneously called");
|
||||
}
|
||||
|
||||
- (void)checkManualAppearanceViewLoaded:(BOOL)isViewLoaded layerBacked:(BOOL)isLayerBacked
|
||||
{
|
||||
// ASDisplayNode visibility does not change if modifying a hierarchy that is not in a window. So create one and add the superview to it.
|
||||
UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectZero];
|
||||
|
||||
DeclareNodeNamed(parent);
|
||||
DeclareNodeNamed(a);
|
||||
DeclareNodeNamed(b);
|
||||
DeclareNodeNamed(aa);
|
||||
DeclareNodeNamed(ab);
|
||||
|
||||
for (ASDisplayNode *n in @[parent, a, b, aa, ab]) {
|
||||
n.layerBacked = isLayerBacked;
|
||||
if (isViewLoaded)
|
||||
[n layer];
|
||||
}
|
||||
|
||||
[parent addSubnode:a];
|
||||
|
||||
XCTAssertFalse(parent.inHierarchy, @"Nothing should be visible");
|
||||
XCTAssertFalse(a.inHierarchy, @"Nothing should be visible");
|
||||
XCTAssertFalse(b.inHierarchy, @"Nothing should be visible");
|
||||
XCTAssertFalse(aa.inHierarchy, @"Nothing should be visible");
|
||||
XCTAssertFalse(ab.inHierarchy, @"Nothing should be visible");
|
||||
|
||||
if (isLayerBacked) {
|
||||
[window.layer addSublayer:parent.layer];
|
||||
} else {
|
||||
[window addSubview:parent.view];
|
||||
}
|
||||
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:parent], 1u, @"Should have -willEnterHierarchy called once");
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:a], 1u, @"Should have -willEnterHierarchy called once");
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:b], 0u, @"Should not have appeared yet");
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:aa], 0u, @"Should not have appeared yet");
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:ab], 0u, @"Should not have appeared yet");
|
||||
|
||||
XCTAssertTrue(parent.inHierarchy, @"Should be visible");
|
||||
XCTAssertTrue(a.inHierarchy, @"Should be visible");
|
||||
XCTAssertFalse(b.inHierarchy, @"Nothing should be visible");
|
||||
XCTAssertFalse(aa.inHierarchy, @"Nothing should be visible");
|
||||
XCTAssertFalse(ab.inHierarchy, @"Nothing should be visible");
|
||||
|
||||
// Add to an already-visible node should make the node visible
|
||||
[parent addSubnode:b];
|
||||
[a insertSubnode:aa atIndex:0];
|
||||
[a insertSubnode:ab aboveSubnode:aa];
|
||||
|
||||
XCTAssertTrue(parent.inHierarchy, @"Should be visible");
|
||||
XCTAssertTrue(a.inHierarchy, @"Should be visible");
|
||||
XCTAssertTrue(b.inHierarchy, @"Should be visible after adding to visible parent");
|
||||
XCTAssertTrue(aa.inHierarchy, @"Nothing should be visible");
|
||||
XCTAssertTrue(ab.inHierarchy, @"Nothing should be visible");
|
||||
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:parent], 1u, @"Should have -willEnterHierarchy called once");
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:a], 1u, @"Should have -willEnterHierarchy called once");
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:b], 1u, @"Should have -willEnterHierarchy called once");
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:aa], 1u, @"Should have -willEnterHierarchy called once");
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:ab], 1u, @"Should have -willEnterHierarchy called once");
|
||||
|
||||
if (isLayerBacked) {
|
||||
[parent.layer removeFromSuperlayer];
|
||||
} else {
|
||||
[parent.view removeFromSuperview];
|
||||
}
|
||||
|
||||
XCTAssertFalse(parent.inHierarchy, @"Nothing should be visible");
|
||||
XCTAssertFalse(a.inHierarchy, @"Nothing should be visible");
|
||||
XCTAssertFalse(b.inHierarchy, @"Nothing should be visible");
|
||||
XCTAssertFalse(aa.inHierarchy, @"Nothing should be visible");
|
||||
XCTAssertFalse(ab.inHierarchy, @"Nothing should be visible");
|
||||
}
|
||||
|
||||
- (void)testAppearanceMethodsNoLayer
|
||||
{
|
||||
[self checkManualAppearanceViewLoaded:NO layerBacked:YES];
|
||||
}
|
||||
|
||||
- (void)testAppearanceMethodsNoView
|
||||
{
|
||||
[self checkManualAppearanceViewLoaded:NO layerBacked:NO];
|
||||
}
|
||||
|
||||
- (void)testAppearanceMethodsLayer
|
||||
{
|
||||
[self checkManualAppearanceViewLoaded:YES layerBacked:YES];
|
||||
}
|
||||
|
||||
- (void)testAppearanceMethodsView
|
||||
{
|
||||
[self checkManualAppearanceViewLoaded:YES layerBacked:NO];
|
||||
}
|
||||
|
||||
- (void)testSynchronousIntermediaryView
|
||||
{
|
||||
// Parent is a wrapper node for a scrollview
|
||||
ASDisplayNode *parentSynchronousNode = [[ASDisplayNode alloc] initWithViewClass:[UIScrollView class]];
|
||||
DeclareNodeNamed(layerBackedNode);
|
||||
DeclareNodeNamed(viewBackedNode);
|
||||
|
||||
layerBackedNode.layerBacked = YES;
|
||||
|
||||
UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectZero];
|
||||
[parentSynchronousNode addSubnode:layerBackedNode];
|
||||
[parentSynchronousNode addSubnode:viewBackedNode];
|
||||
|
||||
XCTAssertFalse(parentSynchronousNode.inHierarchy, @"Should not yet be visible");
|
||||
XCTAssertFalse(layerBackedNode.inHierarchy, @"Should not yet be visible");
|
||||
XCTAssertFalse(viewBackedNode.inHierarchy, @"Should not yet be visible");
|
||||
|
||||
[window addSubview:parentSynchronousNode.view];
|
||||
|
||||
// This is a known case that isn't supported
|
||||
XCTAssertFalse(parentSynchronousNode.inHierarchy, @"Synchronous views are not currently marked visible");
|
||||
|
||||
XCTAssertTrue(layerBackedNode.inHierarchy, @"Synchronous views' subviews should get marked visible");
|
||||
XCTAssertTrue(viewBackedNode.inHierarchy, @"Synchronous views' subviews should get marked visible");
|
||||
|
||||
// Try moving a node to/from a synchronous node in the window with the node API
|
||||
// Setup
|
||||
[layerBackedNode removeFromSupernode];
|
||||
[viewBackedNode removeFromSupernode];
|
||||
XCTAssertFalse(layerBackedNode.inHierarchy, @"aoeu");
|
||||
XCTAssertFalse(viewBackedNode.inHierarchy, @"aoeu");
|
||||
|
||||
// now move to synchronous node
|
||||
[parentSynchronousNode addSubnode:layerBackedNode];
|
||||
[parentSynchronousNode insertSubnode:viewBackedNode aboveSubnode:layerBackedNode];
|
||||
XCTAssertTrue(layerBackedNode.inHierarchy, @"Synchronous views' subviews should get marked visible");
|
||||
XCTAssertTrue(viewBackedNode.inHierarchy, @"Synchronous views' subviews should get marked visible");
|
||||
|
||||
[parentSynchronousNode.view removeFromSuperview];
|
||||
|
||||
XCTAssertFalse(parentSynchronousNode.inHierarchy, @"Should not have changed");
|
||||
XCTAssertFalse(layerBackedNode.inHierarchy, @"Should have been marked invisible when synchronous superview was removed from the window");
|
||||
XCTAssertFalse(viewBackedNode.inHierarchy, @"Should have been marked invisible when synchronous superview was removed from the window");
|
||||
}
|
||||
|
||||
- (void)checkMoveAcrossHierarchyLayerBacked:(BOOL)isLayerBacked useManualCalls:(BOOL)useManualDisable useNodeAPI:(BOOL)useNodeAPI
|
||||
{
|
||||
UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectZero];
|
||||
|
||||
DeclareNodeNamed(parentA);
|
||||
DeclareNodeNamed(parentB);
|
||||
DeclareNodeNamed(child);
|
||||
DeclareNodeNamed(childSubnode);
|
||||
|
||||
for (ASDisplayNode *n in @[parentA, parentB, child, childSubnode]) {
|
||||
n.layerBacked = isLayerBacked;
|
||||
}
|
||||
|
||||
[parentA addSubnode:child];
|
||||
[child addSubnode:childSubnode];
|
||||
|
||||
XCTAssertFalse(parentA.inHierarchy, @"Should not yet be visible");
|
||||
XCTAssertFalse(parentB.inHierarchy, @"Should not yet be visible");
|
||||
XCTAssertFalse(child.inHierarchy, @"Should not yet be visible");
|
||||
XCTAssertFalse(childSubnode.inHierarchy, @"Should not yet be visible");
|
||||
XCTAssertFalse(childSubnode.inHierarchy, @"Should not yet be visible");
|
||||
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:child], 0u, @"Should not have -willEnterHierarchy called");
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:childSubnode], 0u, @"Should not have -willEnterHierarchy called");
|
||||
|
||||
if (isLayerBacked) {
|
||||
[window.layer addSublayer:parentA.layer];
|
||||
[window.layer addSublayer:parentB.layer];
|
||||
} else {
|
||||
[window addSubview:parentA.view];
|
||||
[window addSubview:parentB.view];
|
||||
}
|
||||
|
||||
XCTAssertTrue(parentA.inHierarchy, @"Should be visible after added to window");
|
||||
XCTAssertTrue(parentB.inHierarchy, @"Should be visible after added to window");
|
||||
XCTAssertTrue(child.inHierarchy, @"Should be visible after parent added to window");
|
||||
XCTAssertTrue(childSubnode.inHierarchy, @"Should be visible after parent added to window");
|
||||
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:child], 1u, @"Should have -willEnterHierarchy called once");
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:childSubnode], 1u, @"Should have -willEnterHierarchy called once");
|
||||
|
||||
// Move subnode from A to B
|
||||
if (useManualDisable) {
|
||||
ASDisplayNodeDisableHierarchyNotifications(child);
|
||||
}
|
||||
if (!useNodeAPI) {
|
||||
[child removeFromSupernode];
|
||||
[parentB addSubnode:child];
|
||||
} else {
|
||||
[parentB addSubnode:child];
|
||||
}
|
||||
if (useManualDisable) {
|
||||
XCTAssertTrue([child __visibilityNotificationsDisabled], @"Should not have re-enabled yet");
|
||||
XCTAssertTrue([child __selfOrParentHasVisibilityNotificationsDisabled], @"Should not have re-enabled yet");
|
||||
ASDisplayNodeEnableHierarchyNotifications(child);
|
||||
}
|
||||
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:child], 1u, @"Should not have -willEnterHierarchy called when moving child around in hierarchy");
|
||||
|
||||
// Move subnode back to A
|
||||
if (useManualDisable) {
|
||||
ASDisplayNodeDisableHierarchyNotifications(child);
|
||||
}
|
||||
if (!useNodeAPI) {
|
||||
[child removeFromSupernode];
|
||||
[parentA insertSubnode:child atIndex:0];
|
||||
} else {
|
||||
[parentA insertSubnode:child atIndex:0];
|
||||
}
|
||||
if (useManualDisable) {
|
||||
XCTAssertTrue([child __visibilityNotificationsDisabled], @"Should not have re-enabled yet");
|
||||
XCTAssertTrue([child __selfOrParentHasVisibilityNotificationsDisabled], @"Should not have re-enabled yet");
|
||||
ASDisplayNodeEnableHierarchyNotifications(child);
|
||||
}
|
||||
|
||||
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:child], 1u, @"Should not have -willEnterHierarchy called when moving child around in hierarchy");
|
||||
|
||||
// Finally, remove subnode
|
||||
[child removeFromSupernode];
|
||||
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:child], 1u, @"Should appear and disappear just once");
|
||||
|
||||
// Make sure that we don't leave these unbalanced
|
||||
XCTAssertFalse([child __visibilityNotificationsDisabled], @"Unbalanced visibility notifications calls");
|
||||
XCTAssertFalse([child __selfOrParentHasVisibilityNotificationsDisabled], @"Should not have re-enabled yet");
|
||||
}
|
||||
|
||||
- (void)testMoveAcrossHierarchyLayer
|
||||
{
|
||||
[self checkMoveAcrossHierarchyLayerBacked:YES useManualCalls:NO useNodeAPI:YES];
|
||||
}
|
||||
|
||||
- (void)testMoveAcrossHierarchyView
|
||||
{
|
||||
[self checkMoveAcrossHierarchyLayerBacked:NO useManualCalls:NO useNodeAPI:YES];
|
||||
}
|
||||
|
||||
- (void)testMoveAcrossHierarchyManualLayer
|
||||
{
|
||||
[self checkMoveAcrossHierarchyLayerBacked:YES useManualCalls:YES useNodeAPI:NO];
|
||||
}
|
||||
|
||||
- (void)testMoveAcrossHierarchyManualView
|
||||
{
|
||||
[self checkMoveAcrossHierarchyLayerBacked:NO useManualCalls:YES useNodeAPI:NO];
|
||||
}
|
||||
|
||||
- (void)testDisableWithNodeAPILayer
|
||||
{
|
||||
[self checkMoveAcrossHierarchyLayerBacked:YES useManualCalls:YES useNodeAPI:YES];
|
||||
}
|
||||
|
||||
- (void)testDisableWithNodeAPIView
|
||||
{
|
||||
[self checkMoveAcrossHierarchyLayerBacked:NO useManualCalls:YES useNodeAPI:YES];
|
||||
}
|
||||
|
||||
- (void)testPreventManualAppearanceMethods
|
||||
{
|
||||
DeclareNodeNamed(n);
|
||||
|
||||
XCTAssertThrows([n willEnterHierarchy], @"Should not allow manually calling appearance methods.");
|
||||
XCTAssertThrows([n didExitHierarchy], @"Should not allow manually calling appearance methods.");
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
//
|
||||
// ASDisplayNodeExtrasTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
|
||||
@interface ASDisplayNodeExtrasTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@interface TestDisplayNode : ASDisplayNode
|
||||
@end
|
||||
|
||||
@implementation TestDisplayNode
|
||||
@end
|
||||
|
||||
@implementation ASDisplayNodeExtrasTests
|
||||
|
||||
- (void)testShallowFindSubnodesOfSubclass {
|
||||
ASDisplayNode *supernode = [[ASDisplayNode alloc] initWithLayerBlock:^CALayer * _Nonnull{
|
||||
return [CALayer layer];
|
||||
}];
|
||||
NSUInteger count = 10;
|
||||
NSMutableArray *expected = [[NSMutableArray alloc] initWithCapacity:count];
|
||||
for (NSUInteger nodeIndex = 0; nodeIndex < count; nodeIndex++) {
|
||||
TestDisplayNode *node = [[TestDisplayNode alloc] initWithLayerBlock:^CALayer * _Nonnull{
|
||||
return [CALayer layer];
|
||||
}];
|
||||
[supernode addSubnode:node];
|
||||
[expected addObject:node];
|
||||
}
|
||||
NSArray *found = ASDisplayNodeFindAllSubnodesOfClass(supernode, [TestDisplayNode class]);
|
||||
XCTAssertEqualObjects(found, expected, @"Expecting %lu %@ nodes, found %lu", (unsigned long)count, [TestDisplayNode class], (unsigned long)found.count);
|
||||
}
|
||||
|
||||
- (void)testDeepFindSubnodesOfSubclass {
|
||||
ASDisplayNode *supernode = [[ASDisplayNode alloc] initWithLayerBlock:^CALayer * _Nonnull{
|
||||
return [CALayer layer];
|
||||
}];
|
||||
|
||||
const NSUInteger count = 2;
|
||||
const NSUInteger levels = 2;
|
||||
const NSUInteger capacity = [[self class] capacityForCount:count levels:levels];
|
||||
NSMutableArray *expected = [[NSMutableArray alloc] initWithCapacity:capacity];
|
||||
|
||||
[[self class] addSubnodesToNode:supernode number:count remainingLevels:levels accumulated:expected];
|
||||
|
||||
NSArray *found = ASDisplayNodeFindAllSubnodesOfClass(supernode, [TestDisplayNode class]);
|
||||
XCTAssertEqualObjects(found, expected, @"Expecting %lu %@ nodes, found %lu", (unsigned long)count, [TestDisplayNode class], (unsigned long)found.count);
|
||||
}
|
||||
|
||||
+ (void)addSubnodesToNode:(ASDisplayNode *)supernode number:(NSUInteger)number remainingLevels:(NSUInteger)level accumulated:(inout NSMutableArray *)expected {
|
||||
if (level == 0) return;
|
||||
for (NSUInteger nodeIndex = 0; nodeIndex < number; nodeIndex++) {
|
||||
TestDisplayNode *node = [[TestDisplayNode alloc] initWithLayerBlock:^CALayer * _Nonnull{
|
||||
return [CALayer layer];
|
||||
}];
|
||||
[supernode addSubnode:node];
|
||||
[expected addObject:node];
|
||||
[self addSubnodesToNode:node number:number remainingLevels:(level - 1) accumulated:expected];
|
||||
}
|
||||
}
|
||||
|
||||
// Graph theory is failing me atm.
|
||||
+ (NSUInteger)capacityForCount:(NSUInteger)count levels:(NSUInteger)level {
|
||||
if (level == 0) return 0;
|
||||
return pow(count, level) + [self capacityForCount:count levels:(level - 1)];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,329 +0,0 @@
|
|||
//
|
||||
// ASDisplayNodeImplicitHierarchyTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
|
||||
#import "ASDisplayNodeTestsHelper.h"
|
||||
|
||||
@interface ASSpecTestDisplayNode : ASDisplayNode
|
||||
|
||||
/**
|
||||
Simple state identifier to allow control of current spec inside of the layoutSpecBlock
|
||||
*/
|
||||
@property (nonatomic) NSNumber *layoutState;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASSpecTestDisplayNode
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_layoutState = @1;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASDisplayNodeImplicitHierarchyTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASDisplayNodeImplicitHierarchyTests
|
||||
|
||||
- (void)testFeatureFlag
|
||||
{
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||
XCTAssertFalse(node.automaticallyManagesSubnodes);
|
||||
|
||||
node.automaticallyManagesSubnodes = YES;
|
||||
XCTAssertTrue(node.automaticallyManagesSubnodes);
|
||||
}
|
||||
|
||||
- (void)testInitialNodeInsertionWithOrdering
|
||||
{
|
||||
static CGSize kSize = {100, 100};
|
||||
|
||||
ASDisplayNode *node1 = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node2 = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node3 = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node4 = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node5 = [[ASDisplayNode alloc] init];
|
||||
|
||||
|
||||
// As we will involve a stack spec we have to give the nodes an intrinsic content size
|
||||
node1.style.preferredSize = kSize;
|
||||
node2.style.preferredSize = kSize;
|
||||
node3.style.preferredSize = kSize;
|
||||
node4.style.preferredSize = kSize;
|
||||
node5.style.preferredSize = kSize;
|
||||
|
||||
ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init];
|
||||
node.automaticallyManagesSubnodes = YES;
|
||||
node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) {
|
||||
ASAbsoluteLayoutSpec *absoluteLayout = [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[node4]];
|
||||
|
||||
ASStackLayoutSpec *stack1 = [[ASStackLayoutSpec alloc] init];
|
||||
[stack1 setChildren:@[node1, node2]];
|
||||
|
||||
ASStackLayoutSpec *stack2 = [[ASStackLayoutSpec alloc] init];
|
||||
[stack2 setChildren:@[node3, absoluteLayout]];
|
||||
|
||||
return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[stack1, stack2, node5]];
|
||||
};
|
||||
|
||||
ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)));
|
||||
[node.view layoutIfNeeded];
|
||||
|
||||
XCTAssertEqual(node.subnodes[0], node1);
|
||||
XCTAssertEqual(node.subnodes[1], node2);
|
||||
XCTAssertEqual(node.subnodes[2], node3);
|
||||
XCTAssertEqual(node.subnodes[3], node4);
|
||||
XCTAssertEqual(node.subnodes[4], node5);
|
||||
}
|
||||
|
||||
- (void)testInitialNodeInsertionWhenEnterPreloadState
|
||||
{
|
||||
static CGSize kSize = {100, 100};
|
||||
|
||||
static NSInteger subnodeCount = 5;
|
||||
NSMutableArray<ASDisplayNode *> *subnodes = [NSMutableArray arrayWithCapacity:subnodeCount];
|
||||
for (NSInteger i = 0; i < subnodeCount; i++) {
|
||||
ASDisplayNode *subnode = [[ASDisplayNode alloc] init];
|
||||
// As we will involve a stack spec we have to give the nodes an intrinsic content size
|
||||
subnode.style.preferredSize = kSize;
|
||||
[subnodes addObject:subnode];
|
||||
}
|
||||
|
||||
ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init];
|
||||
[node setHierarchyState:ASHierarchyStateRangeManaged];
|
||||
node.automaticallyManagesSubnodes = YES;
|
||||
node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) {
|
||||
ASAbsoluteLayoutSpec *absoluteLayout = [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[subnodes[3]]];
|
||||
|
||||
ASStackLayoutSpec *stack1 = [[ASStackLayoutSpec alloc] init];
|
||||
[stack1 setChildren:@[subnodes[0], subnodes[1]]];
|
||||
|
||||
ASStackLayoutSpec *stack2 = [[ASStackLayoutSpec alloc] init];
|
||||
[stack2 setChildren:@[subnodes[2], absoluteLayout]];
|
||||
|
||||
return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[stack1, stack2, subnodes[4]]];
|
||||
};
|
||||
|
||||
ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)));
|
||||
[node recursivelySetInterfaceState:ASInterfaceStatePreload];
|
||||
|
||||
ASCATransactionQueueWait(nil);
|
||||
// No premature view allocation
|
||||
XCTAssertFalse(node.isNodeLoaded);
|
||||
// Subnodes should be inserted, laid out and entered preload state
|
||||
XCTAssertTrue([subnodes isEqualToArray:node.subnodes]);
|
||||
for (NSInteger i = 0; i < subnodeCount; i++) {
|
||||
ASDisplayNode *subnode = subnodes[i];
|
||||
XCTAssertTrue(CGSizeEqualToSize(kSize, subnode.bounds.size));
|
||||
XCTAssertTrue(ASInterfaceStateIncludesPreload(subnode.interfaceState));
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testCalculatedLayoutHierarchyTransitions
|
||||
{
|
||||
static CGSize kSize = {100, 100};
|
||||
|
||||
ASDisplayNode *node1 = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node2 = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node3 = [[ASDisplayNode alloc] init];
|
||||
|
||||
node1.debugName = @"a";
|
||||
node2.debugName = @"b";
|
||||
node3.debugName = @"c";
|
||||
|
||||
// As we will involve a stack spec we have to give the nodes an intrinsic content size
|
||||
node1.style.preferredSize = kSize;
|
||||
node2.style.preferredSize = kSize;
|
||||
node3.style.preferredSize = kSize;
|
||||
|
||||
ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init];
|
||||
node.automaticallyManagesSubnodes = YES;
|
||||
node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize){
|
||||
ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode;
|
||||
if ([strongNode.layoutState isEqualToNumber:@1]) {
|
||||
return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[node1, node2]];
|
||||
} else {
|
||||
ASStackLayoutSpec *stackLayout = [[ASStackLayoutSpec alloc] init];
|
||||
[stackLayout setChildren:@[node3, node2]];
|
||||
return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[node1, stackLayout]];
|
||||
}
|
||||
};
|
||||
|
||||
ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)));
|
||||
[node.view layoutIfNeeded];
|
||||
XCTAssertEqual(node.subnodes[0], node1);
|
||||
XCTAssertEqual(node.subnodes[1], node2);
|
||||
|
||||
node.layoutState = @2;
|
||||
[node setNeedsLayout]; // After a state change the layout needs to be invalidated
|
||||
[node.view layoutIfNeeded]; // A new layout pass will trigger the hiearchy transition
|
||||
|
||||
XCTAssertEqual(node.subnodes[0], node1);
|
||||
XCTAssertEqual(node.subnodes[1], node3);
|
||||
XCTAssertEqual(node.subnodes[2], node2);
|
||||
}
|
||||
|
||||
// Disable test for now as we disabled the assertion
|
||||
//- (void)testLayoutTransitionWillThrowForManualSubnodeManagement
|
||||
//{
|
||||
// ASDisplayNode *node1 = [[ASDisplayNode alloc] init];
|
||||
// node1.name = @"node1";
|
||||
//
|
||||
// ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init];
|
||||
// node.automaticallyManagesSubnodes = YES;
|
||||
// node.layoutSpecBlock = ^ASLayoutSpec *(ASDisplayNode *weakNode, ASSizeRange constrainedSize){
|
||||
// return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[node1]];
|
||||
// };
|
||||
//
|
||||
// XCTAssertNoThrow([node layoutThatFits:ASSizeRangeMake(CGSizeZero)]);
|
||||
// XCTAssertThrows([node1 removeFromSupernode]);
|
||||
//}
|
||||
|
||||
- (void)testLayoutTransitionMeasurementCompletionBlockIsCalledOnMainThread
|
||||
{
|
||||
const CGSize kSize = CGSizeMake(100, 100);
|
||||
|
||||
ASDisplayNode *displayNode = [[ASDisplayNode alloc] init];
|
||||
displayNode.style.preferredSize = kSize;
|
||||
|
||||
// Trigger explicit view creation to be able to use the Transition API
|
||||
[displayNode view];
|
||||
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Call measurement completion block on main"];
|
||||
|
||||
[displayNode transitionLayoutWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY)) animated:YES shouldMeasureAsync:YES measurementCompletion:^{
|
||||
XCTAssertTrue(ASDisplayNodeThreadIsMain(), @"Measurement completion block should be called on main thread");
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectationsWithTimeout:2.0 handler:nil];
|
||||
}
|
||||
|
||||
- (void)testMeasurementInBackgroundThreadWithLoadedNode
|
||||
{
|
||||
const CGSize kNodeSize = CGSizeMake(100, 100);
|
||||
ASDisplayNode *node1 = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node2 = [[ASDisplayNode alloc] init];
|
||||
|
||||
ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init];
|
||||
node.style.preferredSize = kNodeSize;
|
||||
node.automaticallyManagesSubnodes = YES;
|
||||
node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) {
|
||||
ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode;
|
||||
if ([strongNode.layoutState isEqualToNumber:@1]) {
|
||||
return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[node1]];
|
||||
} else {
|
||||
return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[node2]];
|
||||
}
|
||||
};
|
||||
|
||||
// Intentionally trigger view creation
|
||||
[node view];
|
||||
[node2 view];
|
||||
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Fix IHM layout also if one node is already loaded"];
|
||||
|
||||
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
|
||||
// Measurement happens in the background
|
||||
ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY)));
|
||||
|
||||
// Dispatch back to the main thread to let the insertion / deletion of subnodes happening
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
// Layout on main
|
||||
[node setNeedsLayout];
|
||||
[node.view layoutIfNeeded];
|
||||
XCTAssertEqual(node.subnodes[0], node1);
|
||||
|
||||
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
|
||||
// Change state and measure in the background
|
||||
node.layoutState = @2;
|
||||
[node setNeedsLayout];
|
||||
|
||||
ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY)));
|
||||
|
||||
// Dispatch back to the main thread to let the insertion / deletion of subnodes happening
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
// Layout on main again
|
||||
[node.view layoutIfNeeded];
|
||||
XCTAssertEqual(node.subnodes[0], node2);
|
||||
|
||||
[expectation fulfill];
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
[self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) {
|
||||
if (error) {
|
||||
NSLog(@"Timeout Error: %@", error);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)testTransitionLayoutWithAnimationWithLoadedNodes
|
||||
{
|
||||
const CGSize kNodeSize = CGSizeMake(100, 100);
|
||||
ASDisplayNode *node1 = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node2 = [[ASDisplayNode alloc] init];
|
||||
|
||||
ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init];
|
||||
node.automaticallyManagesSubnodes = YES;
|
||||
node.style.preferredSize = kNodeSize;
|
||||
node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) {
|
||||
ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode;
|
||||
if ([strongNode.layoutState isEqualToNumber:@1]) {
|
||||
return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[node1]];
|
||||
} else {
|
||||
return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[node2]];
|
||||
}
|
||||
};
|
||||
|
||||
// Intentionally trigger view creation
|
||||
[node1 view];
|
||||
[node2 view];
|
||||
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Fix IHM layout transition also if one node is already loaded"];
|
||||
|
||||
ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY)));
|
||||
[node.view layoutIfNeeded];
|
||||
XCTAssertEqual(node.subnodes[0], node1);
|
||||
|
||||
node.layoutState = @2;
|
||||
[node invalidateCalculatedLayout];
|
||||
[node transitionLayoutWithAnimation:YES shouldMeasureAsync:YES measurementCompletion:^{
|
||||
// Push this to the next runloop to let async insertion / removing of nodes finished before checking
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
XCTAssertEqual(node.subnodes[0], node2);
|
||||
[expectation fulfill];
|
||||
});
|
||||
}];
|
||||
|
||||
[self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) {
|
||||
if (error) {
|
||||
NSLog(@"Timeout Error: %@", error);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,175 +0,0 @@
|
|||
//
|
||||
// ASDisplayNodeLayoutTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASXCTExtensions.h"
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
#import <stdatomic.h>
|
||||
|
||||
#import "ASLayoutSpecSnapshotTestsHelper.h"
|
||||
|
||||
@interface ASDisplayNodeLayoutTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation ASDisplayNodeLayoutTests
|
||||
|
||||
- (void)testMeasureOnLayoutIfNotHappenedBefore
|
||||
{
|
||||
CGSize nodeSize = CGSizeMake(100, 100);
|
||||
|
||||
ASDisplayNode *displayNode = [[ASDisplayNode alloc] init];
|
||||
displayNode.style.width = ASDimensionMake(100);
|
||||
displayNode.style.height = ASDimensionMake(100);
|
||||
|
||||
// Use a button node in here as ASButtonNode uses layoutSpecThatFits:
|
||||
ASButtonNode *buttonNode = [ASButtonNode new];
|
||||
[displayNode addSubnode:buttonNode];
|
||||
|
||||
displayNode.frame = {.size = nodeSize};
|
||||
buttonNode.frame = {.size = nodeSize};
|
||||
|
||||
ASXCTAssertEqualSizes(displayNode.calculatedSize, CGSizeZero, @"Calculated size before measurement and layout should be 0");
|
||||
ASXCTAssertEqualSizes(buttonNode.calculatedSize, CGSizeZero, @"Calculated size before measurement and layout should be 0");
|
||||
|
||||
// Trigger view creation and layout pass without a manual -layoutThatFits: call before so the automatic measurement
|
||||
// pass will trigger in the layout pass
|
||||
[displayNode.view layoutIfNeeded];
|
||||
|
||||
ASXCTAssertEqualSizes(displayNode.calculatedSize, nodeSize, @"Automatic measurement pass should have happened in layout pass");
|
||||
ASXCTAssertEqualSizes(buttonNode.calculatedSize, nodeSize, @"Automatic measurement pass should have happened in layout pass");
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
- (void)testNotAllowAddingSubnodesInLayoutSpecThatFits
|
||||
{
|
||||
ASDisplayNode *displayNode = [ASDisplayNode new];
|
||||
ASDisplayNode *someOtherNode = [ASDisplayNode new];
|
||||
|
||||
displayNode.layoutSpecBlock = ^(ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
[node addSubnode:someOtherNode];
|
||||
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:someOtherNode];
|
||||
};
|
||||
|
||||
XCTAssertThrows([displayNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 100))], @"Should throw if subnode was added in layoutSpecThatFits:");
|
||||
}
|
||||
|
||||
- (void)testNotAllowModifyingSubnodesInLayoutSpecThatFits
|
||||
{
|
||||
ASDisplayNode *displayNode = [ASDisplayNode new];
|
||||
ASDisplayNode *someOtherNode = [ASDisplayNode new];
|
||||
|
||||
[displayNode addSubnode:someOtherNode];
|
||||
|
||||
displayNode.layoutSpecBlock = ^(ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
[someOtherNode removeFromSupernode];
|
||||
[node addSubnode:[ASDisplayNode new]];
|
||||
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:someOtherNode];
|
||||
};
|
||||
|
||||
XCTAssertThrows([displayNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 100))], @"Should throw if subnodes where modified in layoutSpecThatFits:");
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)testMeasureOnLayoutIfNotHappenedBeforeNoRemeasureForSameBounds
|
||||
{
|
||||
CGSize nodeSize = CGSizeMake(100, 100);
|
||||
|
||||
ASDisplayNode *displayNode = [ASDisplayNode new];
|
||||
displayNode.style.width = ASDimensionMake(nodeSize.width);
|
||||
displayNode.style.height = ASDimensionMake(nodeSize.height);
|
||||
|
||||
ASButtonNode *buttonNode = [ASButtonNode new];
|
||||
[displayNode addSubnode:buttonNode];
|
||||
|
||||
__block atomic_int numberOfLayoutSpecThatFitsCalls = ATOMIC_VAR_INIT(0);
|
||||
displayNode.layoutSpecBlock = ^(ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
atomic_fetch_add(&numberOfLayoutSpecThatFitsCalls, 1);
|
||||
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:buttonNode];
|
||||
};
|
||||
|
||||
displayNode.frame = {.size = nodeSize};
|
||||
|
||||
// Trigger initial layout pass without a measurement pass before
|
||||
[displayNode.view layoutIfNeeded];
|
||||
XCTAssertEqual(numberOfLayoutSpecThatFitsCalls, 1, @"Should measure during layout if not measured");
|
||||
|
||||
[displayNode layoutThatFits:ASSizeRangeMake(nodeSize, nodeSize)];
|
||||
XCTAssertEqual(numberOfLayoutSpecThatFitsCalls, 1, @"Should not remeasure with same bounds");
|
||||
}
|
||||
|
||||
- (void)testThatLayoutWithInvalidSizeCausesException
|
||||
{
|
||||
ASDisplayNode *displayNode = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||
node.layoutSpecBlock = ^ASLayoutSpec *(ASDisplayNode *node, ASSizeRange constrainedSize) {
|
||||
return [ASWrapperLayoutSpec wrapperWithLayoutElement:displayNode];
|
||||
};
|
||||
|
||||
XCTAssertThrows([node layoutThatFits:ASSizeRangeMake(CGSizeMake(0, FLT_MAX))]);
|
||||
}
|
||||
|
||||
- (void)testThatLayoutCreatedWithInvalidSizeCausesException
|
||||
{
|
||||
ASDisplayNode *displayNode = [[ASDisplayNode alloc] init];
|
||||
XCTAssertThrows([ASLayout layoutWithLayoutElement:displayNode size:CGSizeMake(FLT_MAX, FLT_MAX)]);
|
||||
XCTAssertThrows([ASLayout layoutWithLayoutElement:displayNode size:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)]);
|
||||
XCTAssertThrows([ASLayout layoutWithLayoutElement:displayNode size:CGSizeMake(INFINITY, INFINITY)]);
|
||||
}
|
||||
|
||||
- (void)testThatLayoutElementCreatedInLayoutSpecThatFitsDoNotGetDeallocated
|
||||
{
|
||||
const CGSize kSize = CGSizeMake(300, 300);
|
||||
|
||||
ASDisplayNode *subNode = [[ASDisplayNode alloc] init];
|
||||
subNode.automaticallyManagesSubnodes = YES;
|
||||
subNode.layoutSpecBlock = ^(ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
ASTextNode *textNode = [ASTextNode new];
|
||||
textNode.attributedText = [[NSAttributedString alloc] initWithString:@"Test Test Test Test Test Test Test Test"];
|
||||
ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:textNode];
|
||||
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:insetSpec];
|
||||
};
|
||||
|
||||
ASDisplayNode *rootNode = [[ASDisplayNode alloc] init];
|
||||
rootNode.automaticallyManagesSubnodes = YES;
|
||||
rootNode.layoutSpecBlock = ^(ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
ASTextNode *textNode = [ASTextNode new];
|
||||
textNode.attributedText = [[NSAttributedString alloc] initWithString:@"Test Test Test Test Test"];
|
||||
ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:textNode];
|
||||
|
||||
return [ASStackLayoutSpec
|
||||
stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical
|
||||
spacing:0.0
|
||||
justifyContent:ASStackLayoutJustifyContentStart
|
||||
alignItems:ASStackLayoutAlignItemsStretch
|
||||
children:@[insetSpec, subNode]];
|
||||
};
|
||||
|
||||
rootNode.frame = CGRectMake(0, 0, kSize.width, kSize.height);
|
||||
[rootNode view];
|
||||
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Execute measure and layout pass"];
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
|
||||
[rootNode layoutThatFits:ASSizeRangeMake(kSize)];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
XCTAssertNoThrow([rootNode.view layoutIfNeeded]);
|
||||
[expectation fulfill];
|
||||
});
|
||||
});
|
||||
|
||||
[self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) {
|
||||
if (error) {
|
||||
XCTFail(@"Expectation failed: %@", error);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
//
|
||||
// ASDisplayNodeSnapshotTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASSnapshotTestCase.h"
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
@interface ASDisplayNodeSnapshotTests : ASSnapshotTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASDisplayNodeSnapshotTests
|
||||
|
||||
- (void)testBasicHierarchySnapshotTesting
|
||||
{
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||
node.backgroundColor = [UIColor blueColor];
|
||||
|
||||
ASTextNode *subnode = [[ASTextNode alloc] init];
|
||||
subnode.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
subnode.attributedText = [[NSAttributedString alloc] initWithString:@"Hello"];
|
||||
node.automaticallyManagesSubnodes = YES;
|
||||
node.layoutSpecBlock = ^(ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(5, 5, 5, 5) child:subnode];
|
||||
};
|
||||
|
||||
ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY)));
|
||||
ASSnapshotVerifyNode(node, nil);
|
||||
}
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,21 +0,0 @@
|
|||
//
|
||||
// ASDisplayNodeTestsHelper.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AsyncDisplayKit/ASDimension.h>
|
||||
|
||||
@class ASCATransactionQueue, ASDisplayNode;
|
||||
|
||||
typedef BOOL (^as_condition_block_t)(void);
|
||||
|
||||
AS_EXTERN BOOL ASDisplayNodeRunRunLoopUntilBlockIsTrue(as_condition_block_t block);
|
||||
|
||||
AS_EXTERN void ASDisplayNodeSizeToFitSize(ASDisplayNode *node, CGSize size);
|
||||
AS_EXTERN void ASDisplayNodeSizeToFitSizeRange(ASDisplayNode *node, ASSizeRange sizeRange);
|
||||
AS_EXTERN void ASCATransactionQueueWait(ASCATransactionQueue *q); // nil means shared queue
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
//
|
||||
// ASDisplayNodeTestsHelper.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASDisplayNodeTestsHelper.h"
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASRunLoopQueue.h>
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
#import <libkern/OSAtomic.h>
|
||||
|
||||
// Poll the condition 1000 times a second.
|
||||
static CFTimeInterval kSingleRunLoopTimeout = 0.001;
|
||||
|
||||
// Time out after 30 seconds.
|
||||
static CFTimeInterval kTimeoutInterval = 30.0f;
|
||||
|
||||
BOOL ASDisplayNodeRunRunLoopUntilBlockIsTrue(as_condition_block_t block)
|
||||
{
|
||||
CFTimeInterval timeoutDate = CACurrentMediaTime() + kTimeoutInterval;
|
||||
BOOL passed = NO;
|
||||
while (true) {
|
||||
OSMemoryBarrier();
|
||||
passed = block();
|
||||
OSMemoryBarrier();
|
||||
if (passed) {
|
||||
break;
|
||||
}
|
||||
CFTimeInterval now = CACurrentMediaTime();
|
||||
if (now > timeoutDate) {
|
||||
break;
|
||||
}
|
||||
// Run until the poll timeout or until timeoutDate, whichever is first.
|
||||
CFTimeInterval runLoopTimeout = MIN(kSingleRunLoopTimeout, timeoutDate - now);
|
||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, runLoopTimeout, true);
|
||||
}
|
||||
return passed;
|
||||
}
|
||||
|
||||
void ASDisplayNodeSizeToFitSize(ASDisplayNode *node, CGSize size)
|
||||
{
|
||||
CGSize sizeThatFits = [node layoutThatFits:ASSizeRangeMake(size)].size;
|
||||
node.bounds = (CGRect){.origin = CGPointZero, .size = sizeThatFits};
|
||||
}
|
||||
|
||||
void ASDisplayNodeSizeToFitSizeRange(ASDisplayNode *node, ASSizeRange sizeRange)
|
||||
{
|
||||
CGSize sizeThatFits = [node layoutThatFits:sizeRange].size;
|
||||
node.bounds = (CGRect){.origin = CGPointZero, .size = sizeThatFits};
|
||||
}
|
||||
|
||||
void ASCATransactionQueueWait(ASCATransactionQueue *q)
|
||||
{
|
||||
if (!q) { q = ASCATransactionQueueGet(); }
|
||||
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:1];
|
||||
BOOL whileResult = YES;
|
||||
while ([date timeIntervalSinceNow] > 0 &&
|
||||
(whileResult = ![q isEmpty])) {
|
||||
[[NSRunLoop currentRunLoop] runUntilDate:
|
||||
[NSDate dateWithTimeIntervalSinceNow:0.01]];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,303 +0,0 @@
|
|||
//
|
||||
// ASDisplayViewAccessibilityTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
#import <AsyncDisplayKit/ASTextNode.h>
|
||||
#import <AsyncDisplayKit/ASConfiguration.h>
|
||||
#import <AsyncDisplayKit/ASConfigurationInternal.h>
|
||||
#import <OCMock/OCMock.h>
|
||||
|
||||
@interface ASDisplayViewAccessibilityTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation ASDisplayViewAccessibilityTests
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
ASConfiguration *config = [[ASConfiguration alloc] initWithDictionary:nil];
|
||||
config.experimentalFeatures = ASExperimentalDisableAccessibilityCache;
|
||||
[ASConfigurationManager test_resetWithConfiguration:config];
|
||||
}
|
||||
|
||||
- (void)testAccessibilityElementsAccessors
|
||||
{
|
||||
// Setup nodes with accessibility info
|
||||
ASDisplayNode *node = nil;
|
||||
ASDisplayNode *subnode = nil;
|
||||
node = [[ASDisplayNode alloc] init];
|
||||
subnode = [[ASDisplayNode alloc] init];
|
||||
NSString *label = @"foo";
|
||||
subnode.isAccessibilityElement = YES;
|
||||
subnode.accessibilityLabel = label;
|
||||
[node addSubnode:subnode];
|
||||
XCTAssertEqualObjects([node.view.accessibilityElements.firstObject accessibilityLabel], label);
|
||||
// NOTE: The following tests will fail unless accessibility is enabled, e.g. by turning the
|
||||
// accessibility inspector on. See https://github.com/TextureGroup/Texture/pull/1069 for details.
|
||||
/*XCTAssertEqualObjects([[node.view accessibilityElementAtIndex:0] accessibilityLabel], label);
|
||||
XCTAssertEqual(node.view.accessibilityElementCount, 1);
|
||||
XCTAssertEqual([node.view indexOfAccessibilityElement:node.view.accessibilityElements.firstObject], 0);*/
|
||||
}
|
||||
|
||||
- (void)testThatSubnodeAccessibilityLabelAggregationWorks
|
||||
{
|
||||
// Setup nodes
|
||||
ASDisplayNode *node = nil;
|
||||
ASDisplayNode *innerNode1 = nil;
|
||||
ASDisplayNode *innerNode2 = nil;
|
||||
node = [[ASDisplayNode alloc] init];
|
||||
innerNode1 = [[ASDisplayNode alloc] init];
|
||||
innerNode2 = [[ASDisplayNode alloc] init];
|
||||
|
||||
// Initialize nodes with relevant accessibility data
|
||||
node.isAccessibilityContainer = YES;
|
||||
innerNode1.accessibilityLabel = @"hello";
|
||||
innerNode2.accessibilityLabel = @"world";
|
||||
|
||||
// Attach the subnodes to the parent node, then ensure their accessibility labels have been'
|
||||
// aggregated to the parent's accessibility label
|
||||
[node addSubnode:innerNode1];
|
||||
[node addSubnode:innerNode2];
|
||||
XCTAssertEqualObjects([node.view.accessibilityElements.firstObject accessibilityLabel],
|
||||
@"hello, world", @"Subnode accessibility label aggregation broken %@",
|
||||
[node.view.accessibilityElements.firstObject accessibilityLabel]);
|
||||
}
|
||||
|
||||
- (void)testThatContainerAccessibilityLabelOverrideStopsAggregation
|
||||
{
|
||||
// Setup nodes
|
||||
ASDisplayNode *node = nil;
|
||||
ASDisplayNode *innerNode = nil;
|
||||
node = [[ASDisplayNode alloc] init];
|
||||
innerNode = [[ASDisplayNode alloc] init];
|
||||
|
||||
// Initialize nodes with relevant accessibility data
|
||||
node.isAccessibilityContainer = YES;
|
||||
node.accessibilityLabel = @"hello";
|
||||
innerNode.accessibilityLabel = @"world";
|
||||
|
||||
// Attach the subnode to the parent node, then ensure the parent's accessibility label does not
|
||||
// get aggregated with the subnode's label
|
||||
[node addSubnode:innerNode];
|
||||
XCTAssertEqualObjects([node.view.accessibilityElements.firstObject accessibilityLabel], @"hello",
|
||||
@"Container accessibility label override broken %@",
|
||||
[node.view.accessibilityElements.firstObject accessibilityLabel]);
|
||||
}
|
||||
|
||||
- (void)testAccessibilityLayerbackedNodesOperationInContainer {
|
||||
ASDisplayNode *contianer = [[ASDisplayNode alloc] init];
|
||||
contianer.frame = CGRectMake(50, 50, 200, 400);
|
||||
contianer.backgroundColor = [UIColor grayColor];
|
||||
contianer.isAccessibilityContainer = YES;
|
||||
// Do any additional setup after loading the view, typically from a nib.
|
||||
ASTextNode *text1 = [[ASTextNode alloc] init];
|
||||
text1.layerBacked = YES;
|
||||
text1.attributedText = [[NSAttributedString alloc] initWithString:@"hello"];
|
||||
text1.frame = CGRectMake(50, 100, 200, 200);
|
||||
[contianer addSubnode:text1];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *elements = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue(elements.count == 1);
|
||||
XCTAssertTrue([[elements.firstObject accessibilityLabel] isEqualToString:@"hello"]);
|
||||
ASTextNode *text2 = [[ASTextNode alloc] init];
|
||||
text2.layerBacked = YES;
|
||||
text2.attributedText = [[NSAttributedString alloc] initWithString:@"world"];
|
||||
text2.frame = CGRectMake(50, 300, 200, 200);
|
||||
[contianer addSubnode:text2];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *updatedElements = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue(updatedElements.count == 1);
|
||||
XCTAssertTrue([[updatedElements.firstObject accessibilityLabel] isEqualToString:@"hello, world"]);
|
||||
ASTextNode *text3 = [[ASTextNode alloc] init];
|
||||
text3.attributedText = [[NSAttributedString alloc] initWithString:@"!!!!"];
|
||||
text3.frame = CGRectMake(50, 400, 200, 100);
|
||||
text3.layerBacked = YES;
|
||||
[text2 addSubnode:text3];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *updatedElements2 = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue([[updatedElements2.firstObject accessibilityLabel] isEqualToString:@"hello, world, !!!!"]);
|
||||
}
|
||||
|
||||
- (void)testAccessibilityNonLayerbackedNodesOperationInContainer
|
||||
{
|
||||
ASDisplayNode *contianer = [[ASDisplayNode alloc] init];
|
||||
contianer.frame = CGRectMake(50, 50, 200, 600);
|
||||
contianer.backgroundColor = [UIColor grayColor];
|
||||
contianer.isAccessibilityContainer = YES;
|
||||
// Do any additional setup after loading the view, typically from a nib.
|
||||
ASTextNode *text1 = [[ASTextNode alloc] init];
|
||||
text1.attributedText = [[NSAttributedString alloc] initWithString:@"hello"];
|
||||
text1.frame = CGRectMake(50, 100, 200, 200);
|
||||
[contianer addSubnode:text1];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *elements = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue(elements.count == 1);
|
||||
XCTAssertTrue([[elements.firstObject accessibilityLabel] isEqualToString:@"hello"]);
|
||||
ASTextNode *text2 = [[ASTextNode alloc] init];
|
||||
text2.attributedText = [[NSAttributedString alloc] initWithString:@"world"];
|
||||
text2.frame = CGRectMake(50, 300, 200, 200);
|
||||
[contianer addSubnode:text2];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *updatedElements = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue(updatedElements.count == 1);
|
||||
XCTAssertTrue([[updatedElements.firstObject accessibilityLabel] isEqualToString:@"hello, world"]);
|
||||
ASTextNode *text3 = [[ASTextNode alloc] init];
|
||||
text3.attributedText = [[NSAttributedString alloc] initWithString:@"!!!!"];
|
||||
text3.frame = CGRectMake(50, 400, 200, 100);
|
||||
[text2 addSubnode:text3];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *updatedElements2 = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue([[updatedElements2.firstObject accessibilityLabel] isEqualToString:@"hello, world, !!!!"]);
|
||||
}
|
||||
|
||||
- (void)testAccessibilityNonLayerbackedNodesOperationInNonContainer
|
||||
{
|
||||
ASDisplayNode *contianer = [[ASDisplayNode alloc] init];
|
||||
contianer.frame = CGRectMake(50, 50, 200, 600);
|
||||
contianer.backgroundColor = [UIColor grayColor];
|
||||
// Do any additional setup after loading the view, typically from a nib.
|
||||
ASTextNode *text1 = [[ASTextNode alloc] init];
|
||||
text1.attributedText = [[NSAttributedString alloc] initWithString:@"hello"];
|
||||
text1.frame = CGRectMake(50, 100, 200, 200);
|
||||
[contianer addSubnode:text1];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *elements = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue(elements.count == 1);
|
||||
XCTAssertTrue([[elements.firstObject accessibilityLabel] isEqualToString:@"hello"]);
|
||||
ASTextNode *text2 = [[ASTextNode alloc] init];
|
||||
text2.attributedText = [[NSAttributedString alloc] initWithString:@"world"];
|
||||
text2.frame = CGRectMake(50, 300, 200, 200);
|
||||
[contianer addSubnode:text2];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *updatedElements = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue(updatedElements.count == 2);
|
||||
XCTAssertTrue([[updatedElements.firstObject accessibilityLabel] isEqualToString:@"hello"]);
|
||||
XCTAssertTrue([[updatedElements.lastObject accessibilityLabel] isEqualToString:@"world"]);
|
||||
ASTextNode *text3 = [[ASTextNode alloc] init];
|
||||
text3.attributedText = [[NSAttributedString alloc] initWithString:@"!!!!"];
|
||||
text3.frame = CGRectMake(50, 400, 200, 100);
|
||||
[text2 addSubnode:text3];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *updatedElements2 = contianer.view.accessibilityElements;
|
||||
//text3 won't be read out cause it's overshadowed by text2
|
||||
XCTAssertTrue(updatedElements2.count == 2);
|
||||
XCTAssertTrue([[updatedElements2.firstObject accessibilityLabel] isEqualToString:@"hello"]);
|
||||
XCTAssertTrue([[updatedElements2.lastObject accessibilityLabel] isEqualToString:@"world"]);
|
||||
}
|
||||
- (void)testAccessibilityLayerbackedNodesOperationInNonContainer
|
||||
{
|
||||
ASDisplayNode *contianer = [[ASDisplayNode alloc] init];
|
||||
contianer.frame = CGRectMake(50, 50, 200, 600);
|
||||
contianer.backgroundColor = [UIColor grayColor];
|
||||
// Do any additional setup after loading the view, typically from a nib.
|
||||
ASTextNode *text1 = [[ASTextNode alloc] init];
|
||||
text1.layerBacked = YES;
|
||||
text1.attributedText = [[NSAttributedString alloc] initWithString:@"hello"];
|
||||
text1.frame = CGRectMake(50, 0, 100, 100);
|
||||
[contianer addSubnode:text1];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *elements = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue(elements.count == 1);
|
||||
XCTAssertTrue([[elements.firstObject accessibilityLabel] isEqualToString:@"hello"]);
|
||||
ASTextNode *text2 = [[ASTextNode alloc] init];
|
||||
text2.layerBacked = YES;
|
||||
text2.attributedText = [[NSAttributedString alloc] initWithString:@"world"];
|
||||
text2.frame = CGRectMake(50, 100, 100, 100);
|
||||
[contianer addSubnode:text2];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *updatedElements = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue(updatedElements.count == 2);
|
||||
XCTAssertTrue([[updatedElements.firstObject accessibilityLabel] isEqualToString:@"hello"]);
|
||||
XCTAssertTrue([[updatedElements.lastObject accessibilityLabel] isEqualToString:@"world"]);
|
||||
ASTextNode *text3 = [[ASTextNode alloc] init];
|
||||
text3.layerBacked = YES;
|
||||
text3.attributedText = [[NSAttributedString alloc] initWithString:@"!!!!"];
|
||||
text3.frame = CGRectMake(50, 200, 100, 100);
|
||||
[text2 addSubnode:text3];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *updatedElements2 = contianer.view.accessibilityElements;
|
||||
//text3 won't be read out cause it's overshadowed by text2
|
||||
XCTAssertTrue(updatedElements2.count == 2);
|
||||
XCTAssertTrue([[updatedElements2.firstObject accessibilityLabel] isEqualToString:@"hello"]);
|
||||
XCTAssertTrue([[updatedElements2.lastObject accessibilityLabel] isEqualToString:@"world"]);
|
||||
}
|
||||
|
||||
- (void)testAccessibilityUpdatesWithElementsChanges
|
||||
{
|
||||
ASDisplayNode *contianer = [[ASDisplayNode alloc] init];
|
||||
contianer.frame = CGRectMake(50, 50, 200, 600);
|
||||
contianer.backgroundColor = [UIColor grayColor];
|
||||
contianer.isAccessibilityContainer = YES;
|
||||
// Do any additional setup after loading the view, typically from a nib.
|
||||
ASTextNode *text1 = [[ASTextNode alloc] init];
|
||||
text1.layerBacked = YES;
|
||||
text1.attributedText = [[NSAttributedString alloc] initWithString:@"hello"];
|
||||
text1.frame = CGRectMake(50, 0, 100, 100);
|
||||
[contianer addSubnode:text1];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *elements = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue(elements.count == 1);
|
||||
XCTAssertTrue([[elements.firstObject accessibilityLabel] isEqualToString:@"hello"]);
|
||||
text1.attributedText = [[NSAttributedString alloc] initWithString:@"greeting"];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *elements2 = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue(elements2.count == 1);
|
||||
XCTAssertTrue([[elements2.firstObject accessibilityLabel] isEqualToString:@"greeting"]);
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark UIAccessibilityAction Forwarding
|
||||
|
||||
- (void)testActionForwarding {
|
||||
ASDisplayNode *node = [ASDisplayNode new];
|
||||
UIView *view = node.view;
|
||||
|
||||
id mockNode = OCMPartialMock(node);
|
||||
|
||||
OCMExpect([mockNode accessibilityActivate]);
|
||||
[view accessibilityActivate];
|
||||
|
||||
OCMExpect([mockNode accessibilityIncrement]);
|
||||
[view accessibilityIncrement];
|
||||
|
||||
OCMExpect([mockNode accessibilityDecrement]);
|
||||
[view accessibilityDecrement];
|
||||
|
||||
OCMExpect([mockNode accessibilityScroll:UIAccessibilityScrollDirectionDown]);
|
||||
[view accessibilityScroll:UIAccessibilityScrollDirectionDown];
|
||||
|
||||
OCMExpect([mockNode accessibilityPerformEscape]);
|
||||
[view accessibilityPerformEscape];
|
||||
|
||||
OCMExpect([mockNode accessibilityPerformMagicTap]);
|
||||
[view accessibilityPerformMagicTap];
|
||||
|
||||
OCMVerifyAll(mockNode);
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,171 +0,0 @@
|
|||
//
|
||||
// ASEditableTextNodeTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASEditableTextNode.h>
|
||||
|
||||
static BOOL CGSizeEqualToSizeWithIn(CGSize size1, CGSize size2, CGFloat delta)
|
||||
{
|
||||
return fabs(size1.width - size2.width) < delta && fabs(size1.height - size2.height) < delta;
|
||||
}
|
||||
|
||||
@interface ASEditableTextNodeTests : XCTestCase
|
||||
@property (nonatomic) ASEditableTextNode *editableTextNode;
|
||||
@property (nonatomic, copy) NSAttributedString *attributedText;
|
||||
@end
|
||||
|
||||
@implementation ASEditableTextNodeTests
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
|
||||
_editableTextNode = [[ASEditableTextNode alloc] init];
|
||||
|
||||
NSMutableAttributedString *mas = [[NSMutableAttributedString alloc] initWithString:@"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."];
|
||||
NSMutableParagraphStyle *para = [NSMutableParagraphStyle new];
|
||||
para.alignment = NSTextAlignmentCenter;
|
||||
para.lineSpacing = 1.0;
|
||||
[mas addAttribute:NSParagraphStyleAttributeName value:para
|
||||
range:NSMakeRange(0, mas.length - 1)];
|
||||
|
||||
// Vary the linespacing on the last line
|
||||
NSMutableParagraphStyle *lastLinePara = [NSMutableParagraphStyle new];
|
||||
lastLinePara.alignment = para.alignment;
|
||||
lastLinePara.lineSpacing = 5.0;
|
||||
[mas addAttribute:NSParagraphStyleAttributeName value:lastLinePara
|
||||
range:NSMakeRange(mas.length - 1, 1)];
|
||||
|
||||
_attributedText = mas;
|
||||
_editableTextNode.attributedText = _attributedText;
|
||||
}
|
||||
|
||||
#pragma mark - ASEditableTextNode
|
||||
|
||||
- (void)testAllocASEditableTextNode
|
||||
{
|
||||
ASEditableTextNode *node = [[ASEditableTextNode alloc] init];
|
||||
XCTAssertTrue([[node class] isSubclassOfClass:[ASEditableTextNode class]], @"ASEditableTextNode alloc should return an instance of ASEditableTextNode, instead returned %@", [node class]);
|
||||
}
|
||||
|
||||
#pragma mark - ASEditableTextNode Tests
|
||||
|
||||
- (void)testUITextInputTraitDefaults
|
||||
{
|
||||
ASEditableTextNode *editableTextNode = [[ASEditableTextNode alloc] init];
|
||||
|
||||
XCTAssertTrue(editableTextNode.autocapitalizationType == UITextAutocapitalizationTypeSentences, @"_ASTextInputTraitsPendingState's autocapitalizationType default should be UITextAutocapitalizationTypeSentences.");
|
||||
XCTAssertTrue(editableTextNode.autocorrectionType == UITextAutocorrectionTypeDefault, @"_ASTextInputTraitsPendingState's autocorrectionType default should be UITextAutocorrectionTypeDefault.");
|
||||
XCTAssertTrue(editableTextNode.spellCheckingType == UITextSpellCheckingTypeDefault, @"_ASTextInputTraitsPendingState's spellCheckingType default should be UITextSpellCheckingTypeDefault.");
|
||||
XCTAssertTrue(editableTextNode.keyboardType == UIKeyboardTypeDefault, @"_ASTextInputTraitsPendingState's keyboardType default should be UIKeyboardTypeDefault.");
|
||||
XCTAssertTrue(editableTextNode.keyboardAppearance == UIKeyboardAppearanceDefault, @"_ASTextInputTraitsPendingState's keyboardAppearance default should be UIKeyboardAppearanceDefault.");
|
||||
XCTAssertTrue(editableTextNode.returnKeyType == UIReturnKeyDefault, @"_ASTextInputTraitsPendingState's returnKeyType default should be UIReturnKeyDefault.");
|
||||
XCTAssertTrue(editableTextNode.enablesReturnKeyAutomatically == NO, @"_ASTextInputTraitsPendingState's enablesReturnKeyAutomatically default should be NO.");
|
||||
XCTAssertTrue(editableTextNode.isSecureTextEntry == NO, @"_ASTextInputTraitsPendingState's isSecureTextEntry default should be NO.");
|
||||
|
||||
XCTAssertTrue(editableTextNode.textView.autocapitalizationType == UITextAutocapitalizationTypeSentences, @"textView's autocapitalizationType default should be UITextAutocapitalizationTypeSentences.");
|
||||
XCTAssertTrue(editableTextNode.textView.autocorrectionType == UITextAutocorrectionTypeDefault, @"textView's autocorrectionType default should be UITextAutocorrectionTypeDefault.");
|
||||
XCTAssertTrue(editableTextNode.textView.spellCheckingType == UITextSpellCheckingTypeDefault, @"textView's spellCheckingType default should be UITextSpellCheckingTypeDefault.");
|
||||
XCTAssertTrue(editableTextNode.textView.keyboardType == UIKeyboardTypeDefault, @"textView's keyboardType default should be UIKeyboardTypeDefault.");
|
||||
XCTAssertTrue(editableTextNode.textView.keyboardAppearance == UIKeyboardAppearanceDefault, @"textView's keyboardAppearance default should be UIKeyboardAppearanceDefault.");
|
||||
XCTAssertTrue(editableTextNode.textView.returnKeyType == UIReturnKeyDefault, @"textView's returnKeyType default should be UIReturnKeyDefault.");
|
||||
XCTAssertTrue(editableTextNode.textView.enablesReturnKeyAutomatically == NO, @"textView's enablesReturnKeyAutomatically default should be NO.");
|
||||
XCTAssertTrue(editableTextNode.textView.isSecureTextEntry == NO, @"textView's isSecureTextEntry default should be NO.");
|
||||
}
|
||||
|
||||
- (void)testUITextInputTraitsSetTraitsBeforeViewLoaded
|
||||
{
|
||||
// UITextView ignores any values set on the first 3 properties below if secureTextEntry is enabled.
|
||||
// Because of this UIKit behavior, we'll test secure entry seperately
|
||||
ASEditableTextNode *editableTextNode = [[ASEditableTextNode alloc] init];
|
||||
|
||||
editableTextNode.autocapitalizationType = UITextAutocapitalizationTypeWords;
|
||||
editableTextNode.autocorrectionType = UITextAutocorrectionTypeYes;
|
||||
editableTextNode.spellCheckingType = UITextSpellCheckingTypeYes;
|
||||
editableTextNode.keyboardType = UIKeyboardTypeTwitter;
|
||||
editableTextNode.keyboardAppearance = UIKeyboardAppearanceDark;
|
||||
editableTextNode.returnKeyType = UIReturnKeyGo;
|
||||
editableTextNode.enablesReturnKeyAutomatically = YES;
|
||||
|
||||
XCTAssertTrue(editableTextNode.textView.autocapitalizationType == UITextAutocapitalizationTypeWords, @"textView's autocapitalizationType should be UITextAutocapitalizationTypeAllCharacters.");
|
||||
XCTAssertTrue(editableTextNode.textView.autocorrectionType == UITextAutocorrectionTypeYes, @"textView's autocorrectionType should be UITextAutocorrectionTypeYes.");
|
||||
XCTAssertTrue(editableTextNode.textView.spellCheckingType == UITextSpellCheckingTypeYes, @"textView's spellCheckingType should be UITextSpellCheckingTypeYes.");
|
||||
XCTAssertTrue(editableTextNode.textView.keyboardType == UIKeyboardTypeTwitter, @"textView's keyboardType should be UIKeyboardTypeTwitter.");
|
||||
XCTAssertTrue(editableTextNode.textView.keyboardAppearance == UIKeyboardAppearanceDark, @"textView's keyboardAppearance should be UIKeyboardAppearanceDark.");
|
||||
XCTAssertTrue(editableTextNode.textView.returnKeyType == UIReturnKeyGo, @"textView's returnKeyType should be UIReturnKeyGo.");
|
||||
XCTAssertTrue(editableTextNode.textView.enablesReturnKeyAutomatically == YES, @"textView's enablesReturnKeyAutomatically should be YES.");
|
||||
|
||||
ASEditableTextNode *secureEditableTextNode = [[ASEditableTextNode alloc] init];
|
||||
secureEditableTextNode.secureTextEntry = YES;
|
||||
|
||||
XCTAssertTrue(secureEditableTextNode.textView.secureTextEntry == YES, @"textView's isSecureTextEntry should be YES.");
|
||||
}
|
||||
|
||||
- (void)testUITextInputTraitsChangeTraitAfterViewLoaded
|
||||
{
|
||||
// UITextView ignores any values set on the first 3 properties below if secureTextEntry is enabled.
|
||||
// Because of this UIKit behavior, we'll test secure entry seperately
|
||||
ASEditableTextNode *editableTextNode = [[ASEditableTextNode alloc] init];
|
||||
|
||||
editableTextNode.textView.autocapitalizationType = UITextAutocapitalizationTypeWords;
|
||||
editableTextNode.textView.autocorrectionType = UITextAutocorrectionTypeYes;
|
||||
editableTextNode.textView.spellCheckingType = UITextSpellCheckingTypeYes;
|
||||
editableTextNode.textView.keyboardType = UIKeyboardTypeTwitter;
|
||||
editableTextNode.textView.keyboardAppearance = UIKeyboardAppearanceDark;
|
||||
editableTextNode.textView.returnKeyType = UIReturnKeyGo;
|
||||
editableTextNode.textView.enablesReturnKeyAutomatically = YES;
|
||||
|
||||
XCTAssertTrue(editableTextNode.textView.autocapitalizationType == UITextAutocapitalizationTypeWords, @"textView's autocapitalizationType should be UITextAutocapitalizationTypeAllCharacters.");
|
||||
XCTAssertTrue(editableTextNode.textView.autocorrectionType == UITextAutocorrectionTypeYes, @"textView's autocorrectionType should be UITextAutocorrectionTypeYes.");
|
||||
XCTAssertTrue(editableTextNode.textView.spellCheckingType == UITextSpellCheckingTypeYes, @"textView's spellCheckingType should be UITextSpellCheckingTypeYes.");
|
||||
XCTAssertTrue(editableTextNode.textView.keyboardType == UIKeyboardTypeTwitter, @"textView's keyboardType should be UIKeyboardTypeTwitter.");
|
||||
XCTAssertTrue(editableTextNode.textView.keyboardAppearance == UIKeyboardAppearanceDark, @"textView's keyboardAppearance should be UIKeyboardAppearanceDark.");
|
||||
XCTAssertTrue(editableTextNode.textView.returnKeyType == UIReturnKeyGo, @"textView's returnKeyType should be UIReturnKeyGo.");
|
||||
XCTAssertTrue(editableTextNode.textView.enablesReturnKeyAutomatically == YES, @"textView's enablesReturnKeyAutomatically should be YES.");
|
||||
|
||||
ASEditableTextNode *secureEditableTextNode = [[ASEditableTextNode alloc] init];
|
||||
secureEditableTextNode.textView.secureTextEntry = YES;
|
||||
|
||||
XCTAssertTrue(secureEditableTextNode.textView.secureTextEntry == YES, @"textView's isSecureTextEntry should be YES.");
|
||||
}
|
||||
|
||||
- (void)testCalculatedSizeIsGreaterThanOrEqualToConstrainedSize
|
||||
{
|
||||
for (NSInteger i = 10; i < 500; i += 50) {
|
||||
CGSize constrainedSize = CGSizeMake(i, i);
|
||||
CGSize calculatedSize = [_editableTextNode layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size;
|
||||
XCTAssertTrue(calculatedSize.width <= constrainedSize.width, @"Calculated width (%f) should be less than or equal to constrained width (%f)", calculatedSize.width, constrainedSize.width);
|
||||
XCTAssertTrue(calculatedSize.height <= constrainedSize.height, @"Calculated height (%f) should be less than or equal to constrained height (%f)", calculatedSize.height, constrainedSize.height);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testRecalculationOfSizeIsSameAsOriginallyCalculatedSize
|
||||
{
|
||||
for (NSInteger i = 10; i < 500; i += 50) {
|
||||
CGSize constrainedSize = CGSizeMake(i, i);
|
||||
CGSize calculatedSize = [_editableTextNode layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size;
|
||||
CGSize recalculatedSize = [_editableTextNode layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size;
|
||||
|
||||
XCTAssertTrue(CGSizeEqualToSizeWithIn(calculatedSize, recalculatedSize, 4.0), @"Recalculated size %@ should be same as original size %@", NSStringFromCGSize(recalculatedSize), NSStringFromCGSize(calculatedSize));
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testRecalculationOfSizeIsSameAsOriginallyCalculatedFloatingPointSize
|
||||
{
|
||||
for (CGFloat i = 10; i < 500; i *= 1.3) {
|
||||
CGSize constrainedSize = CGSizeMake(i, i);
|
||||
CGSize calculatedSize = [_editableTextNode layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size;
|
||||
CGSize recalculatedSize = [_editableTextNode layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size;
|
||||
|
||||
XCTAssertTrue(CGSizeEqualToSizeWithIn(calculatedSize, recalculatedSize, 11.0), @"Recalculated size %@ should be same as original size %@", NSStringFromCGSize(recalculatedSize), NSStringFromCGSize(calculatedSize));
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
//
|
||||
// ASImageNodeSnapshotTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASSnapshotTestCase.h"
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
@interface ASImageNodeSnapshotTests : ASSnapshotTestCase
|
||||
@end
|
||||
|
||||
@implementation ASImageNodeSnapshotTests
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
|
||||
self.recordMode = NO;
|
||||
}
|
||||
|
||||
- (UIImage *)testImage
|
||||
{
|
||||
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"logo-square"
|
||||
ofType:@"png"
|
||||
inDirectory:@"TestResources"];
|
||||
return [UIImage imageWithContentsOfFile:path];
|
||||
}
|
||||
|
||||
- (void)testRenderLogoSquare
|
||||
{
|
||||
// trivial test case to ensure ASSnapshotTestCase works
|
||||
ASImageNode *imageNode = [[ASImageNode alloc] init];
|
||||
imageNode.image = [self testImage];
|
||||
ASDisplayNodeSizeToFitSize(imageNode, CGSizeMake(100, 100));
|
||||
|
||||
ASSnapshotVerifyNode(imageNode, nil);
|
||||
}
|
||||
|
||||
- (void)testForcedScaling
|
||||
{
|
||||
CGSize forcedImageSize = CGSizeMake(100, 100);
|
||||
|
||||
ASImageNode *imageNode = [[ASImageNode alloc] init];
|
||||
imageNode.forcedSize = forcedImageSize;
|
||||
imageNode.image = [self testImage];
|
||||
|
||||
// Snapshot testing requires that node is formally laid out.
|
||||
imageNode.style.width = ASDimensionMake(forcedImageSize.width);
|
||||
imageNode.style.height = ASDimensionMake(forcedImageSize.height);
|
||||
ASDisplayNodeSizeToFitSize(imageNode, forcedImageSize);
|
||||
ASSnapshotVerifyNode(imageNode, @"first");
|
||||
|
||||
imageNode.style.width = ASDimensionMake(200);
|
||||
imageNode.style.height = ASDimensionMake(200);
|
||||
ASDisplayNodeSizeToFitSize(imageNode, CGSizeMake(200, 200));
|
||||
ASSnapshotVerifyNode(imageNode, @"second");
|
||||
|
||||
XCTAssert(CGImageGetWidth((CGImageRef)imageNode.contents) == forcedImageSize.width * imageNode.contentsScale &&
|
||||
CGImageGetHeight((CGImageRef)imageNode.contents) == forcedImageSize.height * imageNode.contentsScale,
|
||||
@"Contents should be 100 x 100 by contents scale.");
|
||||
}
|
||||
|
||||
- (void)testTintColorBlock
|
||||
{
|
||||
UIImage *test = [self testImage];
|
||||
UIImage *tinted = ASImageNodeTintColorModificationBlock([UIColor redColor])(test);
|
||||
ASImageNode *node = [[ASImageNode alloc] init];
|
||||
node.image = tinted;
|
||||
ASDisplayNodeSizeToFitSize(node, test.size);
|
||||
|
||||
ASSnapshotVerifyNode(node, nil);
|
||||
}
|
||||
|
||||
- (void)testRoundedCornerBlock
|
||||
{
|
||||
UIGraphicsBeginImageContext(CGSizeMake(100, 100));
|
||||
[[UIColor blueColor] setFill];
|
||||
UIRectFill(CGRectMake(0, 0, 100, 100));
|
||||
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
UIImage *rounded = ASImageNodeRoundBorderModificationBlock(2, [UIColor redColor])(result);
|
||||
ASImageNode *node = [[ASImageNode alloc] init];
|
||||
node.image = rounded;
|
||||
ASDisplayNodeSizeToFitSize(node, rounded.size);
|
||||
|
||||
ASSnapshotVerifyNode(node, nil);
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
//
|
||||
// ASInsetLayoutSpecSnapshotTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutSpecSnapshotTestsHelper.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASBackgroundLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASInsetLayoutSpec.h>
|
||||
|
||||
typedef NS_OPTIONS(NSUInteger, ASInsetLayoutSpecTestEdge) {
|
||||
ASInsetLayoutSpecTestEdgeTop = 1 << 0,
|
||||
ASInsetLayoutSpecTestEdgeLeft = 1 << 1,
|
||||
ASInsetLayoutSpecTestEdgeBottom = 1 << 2,
|
||||
ASInsetLayoutSpecTestEdgeRight = 1 << 3,
|
||||
};
|
||||
|
||||
static CGFloat insetForEdge(NSUInteger combination, ASInsetLayoutSpecTestEdge edge, CGFloat insetValue)
|
||||
{
|
||||
return combination & edge ? INFINITY : insetValue;
|
||||
}
|
||||
|
||||
static UIEdgeInsets insetsForCombination(NSUInteger combination, CGFloat insetValue)
|
||||
{
|
||||
return {
|
||||
.top = insetForEdge(combination, ASInsetLayoutSpecTestEdgeTop, insetValue),
|
||||
.left = insetForEdge(combination, ASInsetLayoutSpecTestEdgeLeft, insetValue),
|
||||
.bottom = insetForEdge(combination, ASInsetLayoutSpecTestEdgeBottom, insetValue),
|
||||
.right = insetForEdge(combination, ASInsetLayoutSpecTestEdgeRight, insetValue),
|
||||
};
|
||||
}
|
||||
|
||||
static NSString *nameForInsets(UIEdgeInsets insets)
|
||||
{
|
||||
return [NSString stringWithFormat:@"%.f-%.f-%.f-%.f", insets.top, insets.left, insets.bottom, insets.right];
|
||||
}
|
||||
|
||||
@interface ASInsetLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase
|
||||
@end
|
||||
|
||||
@implementation ASInsetLayoutSpecSnapshotTests
|
||||
|
||||
- (void)testInsetsWithVariableSize
|
||||
{
|
||||
for (NSUInteger combination = 0; combination < 16; combination++) {
|
||||
UIEdgeInsets insets = insetsForCombination(combination, 10);
|
||||
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor grayColor]);
|
||||
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor greenColor], {10, 10});
|
||||
|
||||
ASLayoutSpec *layoutSpec =
|
||||
[ASBackgroundLayoutSpec
|
||||
backgroundLayoutSpecWithChild:
|
||||
[ASInsetLayoutSpec
|
||||
insetLayoutSpecWithInsets:insets
|
||||
child:foregroundNode]
|
||||
background:backgroundNode];
|
||||
|
||||
static ASSizeRange kVariableSize = {{0, 0}, {300, 300}};
|
||||
[self testLayoutSpec:layoutSpec
|
||||
sizeRange:kVariableSize
|
||||
subnodes:@[backgroundNode, foregroundNode]
|
||||
identifier:nameForInsets(insets)];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testInsetsWithFixedSize
|
||||
{
|
||||
for (NSUInteger combination = 0; combination < 16; combination++) {
|
||||
UIEdgeInsets insets = insetsForCombination(combination, 10);
|
||||
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor grayColor]);
|
||||
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor greenColor], {10, 10});
|
||||
|
||||
ASLayoutSpec *layoutSpec =
|
||||
[ASBackgroundLayoutSpec
|
||||
backgroundLayoutSpecWithChild:
|
||||
[ASInsetLayoutSpec
|
||||
insetLayoutSpecWithInsets:insets
|
||||
child:foregroundNode]
|
||||
background:backgroundNode];
|
||||
|
||||
static ASSizeRange kFixedSize = {{300, 300}, {300, 300}};
|
||||
[self testLayoutSpec:layoutSpec
|
||||
sizeRange:kFixedSize
|
||||
subnodes:@[backgroundNode, foregroundNode]
|
||||
identifier:nameForInsets(insets)];
|
||||
}
|
||||
}
|
||||
|
||||
/** Regression test, there was a bug mixing insets with infinite and zero sizes */
|
||||
- (void)testInsetsWithInfinityAndZeroInsetValue
|
||||
{
|
||||
for (NSUInteger combination = 0; combination < 16; combination++) {
|
||||
UIEdgeInsets insets = insetsForCombination(combination, 0);
|
||||
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor grayColor]);
|
||||
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor greenColor], {10, 10});
|
||||
|
||||
ASLayoutSpec *layoutSpec =
|
||||
[ASBackgroundLayoutSpec
|
||||
backgroundLayoutSpecWithChild:
|
||||
[ASInsetLayoutSpec
|
||||
insetLayoutSpecWithInsets:insets
|
||||
child:foregroundNode]
|
||||
background:backgroundNode];
|
||||
|
||||
static ASSizeRange kFixedSize = {{300, 300}, {300, 300}};
|
||||
[self testLayoutSpec:layoutSpec
|
||||
sizeRange:kFixedSize
|
||||
subnodes:@[backgroundNode, foregroundNode]
|
||||
identifier:nameForInsets(insets)];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
//
|
||||
// ASIntegerMapTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASTestCase.h"
|
||||
#import "ASIntegerMap.h"
|
||||
|
||||
@interface ASIntegerMapTests : ASTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASIntegerMapTests
|
||||
|
||||
- (void)testIsEqual
|
||||
{
|
||||
ASIntegerMap *map = [[ASIntegerMap alloc] init];
|
||||
[map setInteger:1 forKey:0];
|
||||
ASIntegerMap *alsoMap = [[ASIntegerMap alloc] init];
|
||||
[alsoMap setInteger:1 forKey:0];
|
||||
ASIntegerMap *notMap = [[ASIntegerMap alloc] init];
|
||||
[notMap setInteger:2 forKey:0];
|
||||
XCTAssertEqualObjects(map, alsoMap);
|
||||
XCTAssertNotEqualObjects(map, notMap);
|
||||
}
|
||||
|
||||
#pragma mark - Changeset mapping
|
||||
|
||||
/// 1 item, no changes -> identity map
|
||||
- (void)testEmptyChange
|
||||
{
|
||||
ASIntegerMap *map = [ASIntegerMap mapForUpdateWithOldCount:1 deleted:nil inserted:nil];
|
||||
XCTAssertEqual(map, ASIntegerMap.identityMap);
|
||||
}
|
||||
|
||||
/// 0 items -> empty map
|
||||
- (void)testChangeOnNoData
|
||||
{
|
||||
ASIntegerMap *map = [ASIntegerMap mapForUpdateWithOldCount:0 deleted:nil inserted:nil];
|
||||
XCTAssertEqual(map, ASIntegerMap.emptyMap);
|
||||
}
|
||||
|
||||
/// 2 items, delete 0
|
||||
- (void)testBasicChange1
|
||||
{
|
||||
ASIntegerMap *map = [ASIntegerMap mapForUpdateWithOldCount:2 deleted:[NSIndexSet indexSetWithIndex:0] inserted:nil];
|
||||
XCTAssertEqual([map integerForKey:0], NSNotFound);
|
||||
XCTAssertEqual([map integerForKey:1], 0);
|
||||
XCTAssertEqual([map integerForKey:2], NSNotFound);
|
||||
}
|
||||
|
||||
/// 2 items, insert 0
|
||||
- (void)testBasicChange2
|
||||
{
|
||||
ASIntegerMap *map = [ASIntegerMap mapForUpdateWithOldCount:2 deleted:nil inserted:[NSIndexSet indexSetWithIndex:0]];
|
||||
XCTAssertEqual([map integerForKey:0], 1);
|
||||
XCTAssertEqual([map integerForKey:1], 2);
|
||||
XCTAssertEqual([map integerForKey:2], NSNotFound);
|
||||
}
|
||||
|
||||
/// 2 items, insert 0, delete 0
|
||||
- (void)testChange1
|
||||
{
|
||||
ASIntegerMap *map = [ASIntegerMap mapForUpdateWithOldCount:2 deleted:[NSIndexSet indexSetWithIndex:0] inserted:[NSIndexSet indexSetWithIndex:0]];
|
||||
XCTAssertEqual([map integerForKey:0], NSNotFound);
|
||||
XCTAssertEqual([map integerForKey:1], 1);
|
||||
XCTAssertEqual([map integerForKey:2], NSNotFound);
|
||||
}
|
||||
|
||||
/// 4 items, insert {0-1, 3}
|
||||
- (void)testChange2
|
||||
{
|
||||
NSMutableIndexSet *inserts = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)];
|
||||
[inserts addIndex:3];
|
||||
ASIntegerMap *map = [ASIntegerMap mapForUpdateWithOldCount:4 deleted:nil inserted:inserts];
|
||||
XCTAssertEqual([map integerForKey:0], 2);
|
||||
XCTAssertEqual([map integerForKey:1], 4);
|
||||
XCTAssertEqual([map integerForKey:2], 5);
|
||||
XCTAssertEqual([map integerForKey:3], 6);
|
||||
}
|
||||
|
||||
/// 4 items, delete {0-1, 3}
|
||||
- (void)testChange3
|
||||
{
|
||||
NSMutableIndexSet *deletes = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)];
|
||||
[deletes addIndex:3];
|
||||
ASIntegerMap *map = [ASIntegerMap mapForUpdateWithOldCount:4 deleted:deletes inserted:nil];
|
||||
XCTAssertEqual([map integerForKey:0], NSNotFound);
|
||||
XCTAssertEqual([map integerForKey:1], NSNotFound);
|
||||
XCTAssertEqual([map integerForKey:2], 0);
|
||||
XCTAssertEqual([map integerForKey:3], NSNotFound);
|
||||
}
|
||||
|
||||
/// 5 items, delete {0-1, 3} insert {1-2, 4}
|
||||
- (void)testChange4
|
||||
{
|
||||
NSMutableIndexSet *deletes = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)];
|
||||
[deletes addIndex:3];
|
||||
NSMutableIndexSet *inserts = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(1, 2)];
|
||||
[inserts addIndex:4];
|
||||
ASIntegerMap *map = [ASIntegerMap mapForUpdateWithOldCount:5 deleted:deletes inserted:inserts];
|
||||
XCTAssertEqual([map integerForKey:0], NSNotFound);
|
||||
XCTAssertEqual([map integerForKey:1], NSNotFound);
|
||||
XCTAssertEqual([map integerForKey:2], 0);
|
||||
XCTAssertEqual([map integerForKey:3], NSNotFound);
|
||||
XCTAssertEqual([map integerForKey:4], 3);
|
||||
XCTAssertEqual([map integerForKey:5], NSNotFound);
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
//
|
||||
// ASLayoutElementStyleTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import "ASXCTExtensions.h"
|
||||
#import <AsyncDisplayKit/ASLayoutElement.h>
|
||||
|
||||
#pragma mark - ASLayoutElementStyleTestsDelegate
|
||||
|
||||
@interface ASLayoutElementStyleTestsDelegate : NSObject<ASLayoutElementStyleDelegate>
|
||||
@property (copy, nonatomic) NSString *propertyNameChanged;
|
||||
@end
|
||||
|
||||
@implementation ASLayoutElementStyleTestsDelegate
|
||||
|
||||
- (void)style:(id)style propertyDidChange:(NSString *)propertyName
|
||||
{
|
||||
self.propertyNameChanged = propertyName;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - ASLayoutElementStyleTests
|
||||
|
||||
@interface ASLayoutElementStyleTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASLayoutElementStyleTests
|
||||
|
||||
- (void)testSettingSize
|
||||
{
|
||||
ASLayoutElementStyle *style = [ASLayoutElementStyle new];
|
||||
|
||||
style.width = ASDimensionMake(100);
|
||||
style.height = ASDimensionMake(100);
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.width, ASDimensionMake(100)));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.height, ASDimensionMake(100)));
|
||||
|
||||
style.minWidth = ASDimensionMake(100);
|
||||
style.minHeight = ASDimensionMake(100);
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.width, ASDimensionMake(100)));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.height, ASDimensionMake(100)));
|
||||
|
||||
style.maxWidth = ASDimensionMake(100);
|
||||
style.maxHeight = ASDimensionMake(100);
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.width, ASDimensionMake(100)));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.height, ASDimensionMake(100)));
|
||||
}
|
||||
|
||||
- (void)testSettingSizeViaCGSize
|
||||
{
|
||||
ASLayoutElementStyle *style = [ASLayoutElementStyle new];
|
||||
|
||||
ASXCTAssertEqualSizes(style.preferredSize, CGSizeZero);
|
||||
|
||||
CGSize size = CGSizeMake(100, 100);
|
||||
|
||||
style.preferredSize = size;
|
||||
ASXCTAssertEqualSizes(style.preferredSize, size);
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.width, ASDimensionMakeWithPoints(size.width)));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.height, ASDimensionMakeWithPoints(size.height)));
|
||||
|
||||
style.minSize = size;
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.minWidth, ASDimensionMakeWithPoints(size.width)));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.minHeight, ASDimensionMakeWithPoints(size.height)));
|
||||
|
||||
style.maxSize = size;
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.maxWidth, ASDimensionMakeWithPoints(size.width)));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.maxHeight, ASDimensionMakeWithPoints(size.height)));
|
||||
}
|
||||
|
||||
- (void)testReadingInvalidSizeForPreferredSize
|
||||
{
|
||||
ASLayoutElementStyle *style = [ASLayoutElementStyle new];
|
||||
|
||||
XCTAssertNoThrow(style.preferredSize);
|
||||
|
||||
style.width = ASDimensionMake(ASDimensionUnitFraction, 0.5);
|
||||
XCTAssertThrows(style.preferredSize);
|
||||
|
||||
style.preferredSize = CGSizeMake(100, 100);
|
||||
XCTAssertNoThrow(style.preferredSize);
|
||||
}
|
||||
|
||||
- (void)testSettingSizeViaLayoutSize
|
||||
{
|
||||
ASLayoutElementStyle *style = [ASLayoutElementStyle new];
|
||||
|
||||
ASLayoutSize layoutSize = ASLayoutSizeMake(ASDimensionMake(100), ASDimensionMake(100));
|
||||
|
||||
style.preferredLayoutSize = layoutSize;
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.width, layoutSize.width));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.height, layoutSize.height));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.preferredLayoutSize.width, layoutSize.width));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.preferredLayoutSize.height, layoutSize.height));
|
||||
|
||||
style.minLayoutSize = layoutSize;
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.minWidth, layoutSize.width));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.minHeight, layoutSize.height));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.minLayoutSize.width, layoutSize.width));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.minLayoutSize.height, layoutSize.height));
|
||||
|
||||
style.maxLayoutSize = layoutSize;
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.maxWidth, layoutSize.width));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.maxHeight, layoutSize.height));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.maxLayoutSize.width, layoutSize.width));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.maxLayoutSize.height, layoutSize.height));
|
||||
}
|
||||
|
||||
- (void)testSettingPropertiesWillCallDelegate
|
||||
{
|
||||
ASLayoutElementStyleTestsDelegate *delegate = [ASLayoutElementStyleTestsDelegate new];
|
||||
ASLayoutElementStyle *style = [[ASLayoutElementStyle alloc] initWithDelegate:delegate];
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.width, ASDimensionAuto));
|
||||
style.width = ASDimensionMake(100);
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.width, ASDimensionMake(100)));
|
||||
XCTAssertTrue([delegate.propertyNameChanged isEqualToString:ASLayoutElementStyleWidthProperty]);
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,591 +0,0 @@
|
|||
//
|
||||
// ASLayoutEngineTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASTestCase.h"
|
||||
#import "ASLayoutTestNode.h"
|
||||
#import "ASXCTExtensions.h"
|
||||
#import "ASTLayoutFixture.h"
|
||||
|
||||
@interface ASLayoutEngineTests : ASTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASLayoutEngineTests {
|
||||
ASLayoutTestNode *nodeA;
|
||||
ASLayoutTestNode *nodeB;
|
||||
ASLayoutTestNode *nodeC;
|
||||
ASLayoutTestNode *nodeD;
|
||||
ASLayoutTestNode *nodeE;
|
||||
ASTLayoutFixture *fixture1;
|
||||
ASTLayoutFixture *fixture2;
|
||||
ASTLayoutFixture *fixture3;
|
||||
ASTLayoutFixture *fixture4;
|
||||
ASTLayoutFixture *fixture5;
|
||||
|
||||
// fixtures 1, 3 and 5 share the same exact node A layout spec block.
|
||||
// we don't want the infra to call -setNeedsLayout when we switch fixtures
|
||||
// so we need to use the same exact block.
|
||||
ASLayoutSpecBlock fixture1and3and5NodeALayoutSpecBlock;
|
||||
|
||||
UIWindow *window;
|
||||
UIViewController *vc;
|
||||
NSArray<ASLayoutTestNode *> *allNodes;
|
||||
NSTimeInterval verifyDelay;
|
||||
// See -stubCalculatedLayoutDidChange.
|
||||
BOOL stubbedCalculatedLayoutDidChange;
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
verifyDelay = 3;
|
||||
window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 10, 1)];
|
||||
vc = [[UIViewController alloc] init];
|
||||
nodeA = [ASLayoutTestNode new];
|
||||
nodeA.backgroundColor = [UIColor redColor];
|
||||
|
||||
// NOTE: nodeB has flexShrink, the others don't
|
||||
nodeB = [ASLayoutTestNode new];
|
||||
nodeB.style.flexShrink = 1;
|
||||
nodeB.backgroundColor = [UIColor orangeColor];
|
||||
|
||||
nodeC = [ASLayoutTestNode new];
|
||||
nodeC.backgroundColor = [UIColor yellowColor];
|
||||
nodeD = [ASLayoutTestNode new];
|
||||
nodeD.backgroundColor = [UIColor greenColor];
|
||||
nodeE = [ASLayoutTestNode new];
|
||||
nodeE.backgroundColor = [UIColor blueColor];
|
||||
allNodes = @[ nodeA, nodeB, nodeC, nodeD, nodeE ];
|
||||
ASSetDebugNames(nodeA, nodeB, nodeC, nodeD, nodeE);
|
||||
ASLayoutSpecBlock b = ^ASLayoutSpec * _Nonnull(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
return [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:0 justifyContent:ASStackLayoutJustifyContentSpaceBetween alignItems:ASStackLayoutAlignItemsStart children:@[ nodeB, nodeC, nodeD ]];
|
||||
};
|
||||
fixture1and3and5NodeALayoutSpecBlock = b;
|
||||
fixture1 = [self createFixture1];
|
||||
fixture2 = [self createFixture2];
|
||||
fixture3 = [self createFixture3];
|
||||
fixture4 = [self createFixture4];
|
||||
fixture5 = [self createFixture5];
|
||||
|
||||
nodeA.frame = vc.view.bounds;
|
||||
nodeA.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
[vc.view addSubnode:nodeA];
|
||||
|
||||
window.rootViewController = vc;
|
||||
[window makeKeyAndVisible];
|
||||
}
|
||||
|
||||
- (void)tearDown
|
||||
{
|
||||
nodeA.layoutSpecBlock = nil;
|
||||
for (ASLayoutTestNode *node in allNodes) {
|
||||
OCMVerifyAllWithDelay(node.mock, verifyDelay);
|
||||
}
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
- (void)testFirstLayoutPassWhenInWindow
|
||||
{
|
||||
[self runFirstLayoutPassWithFixture:fixture1];
|
||||
}
|
||||
|
||||
- (void)testSetNeedsLayoutAndNormalLayoutPass
|
||||
{
|
||||
[self runFirstLayoutPassWithFixture:fixture1];
|
||||
|
||||
[fixture2 apply];
|
||||
|
||||
// skip nodeB because its layout doesn't change.
|
||||
for (ASLayoutTestNode *node in @[ nodeA, nodeC, nodeE ]) {
|
||||
[fixture2 withSizeRangesForNode:node block:^(ASSizeRange sizeRange) {
|
||||
OCMExpect([node.mock calculateLayoutThatFits:sizeRange]).onMainThread();
|
||||
}];
|
||||
OCMExpect([node.mock calculatedLayoutDidChange]).onMainThread();
|
||||
}
|
||||
|
||||
[window layoutIfNeeded];
|
||||
[self verifyFixture:fixture2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition from fixture1 to Fixture2 on node A.
|
||||
*
|
||||
* Expect A and D to calculate once off main, and
|
||||
* to receive calculatedLayoutDidChange on main,
|
||||
* then to get the measurement completion call on main,
|
||||
* then to get animateLayoutTransition: and didCompleteLayoutTransition: on main.
|
||||
*/
|
||||
- (void)testLayoutTransitionWithAsyncMeasurement
|
||||
{
|
||||
[self stubCalculatedLayoutDidChange];
|
||||
[self runFirstLayoutPassWithFixture:fixture1];
|
||||
|
||||
[fixture2 apply];
|
||||
|
||||
// Expect A, C, E to calculate new layouts off-main
|
||||
// dispatch_once onto main to run our injectedMainThread work while the transition calculates.
|
||||
__block dispatch_block_t injectedMainThreadWork = nil;
|
||||
for (ASLayoutTestNode *node in @[ nodeA, nodeC, nodeE ]) {
|
||||
[fixture2 withSizeRangesForNode:node block:^(ASSizeRange sizeRange) {
|
||||
OCMExpect([node.mock calculateLayoutThatFits:sizeRange])
|
||||
.offMainThread()
|
||||
.andDo(^(NSInvocation *inv) {
|
||||
// On first calculateLayoutThatFits, schedule our injected main thread work.
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
injectedMainThreadWork();
|
||||
});
|
||||
});
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
// The code in this section is designed to move in time order, all on the main thread:
|
||||
|
||||
OCMExpect([nodeA.mock animateLayoutTransition:OCMOCK_ANY]).onMainThread();
|
||||
OCMExpect([nodeA.mock didCompleteLayoutTransition:OCMOCK_ANY]).onMainThread();
|
||||
|
||||
// Trigger the layout transition.
|
||||
__block dispatch_block_t measurementCompletionBlock = nil;
|
||||
[nodeA transitionLayoutWithAnimation:NO shouldMeasureAsync:YES measurementCompletion:^{
|
||||
measurementCompletionBlock();
|
||||
}];
|
||||
|
||||
// This block will get run after bg layout calculate starts, but before measurementCompletion
|
||||
__block BOOL injectedMainThreadWorkDone = NO;
|
||||
injectedMainThreadWork = ^{
|
||||
injectedMainThreadWorkDone = YES;
|
||||
|
||||
[window layoutIfNeeded];
|
||||
|
||||
// Ensure we're still on the old layout. We should stay on this until the transition completes.
|
||||
[self verifyFixture:fixture1];
|
||||
};
|
||||
|
||||
measurementCompletionBlock = ^{
|
||||
XCTAssert(injectedMainThreadWorkDone, @"We hoped to get onto the main thread before the measurementCompletion callback ran.");
|
||||
};
|
||||
|
||||
for (ASLayoutTestNode *node in allNodes) {
|
||||
OCMVerifyAllWithDelay(node.mock, verifyDelay);
|
||||
}
|
||||
|
||||
[self verifyFixture:fixture2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition from fixture1 to Fixture2 on node A.
|
||||
*
|
||||
* Expect A and D to calculate once on main, and
|
||||
* to receive calculatedLayoutDidChange on main,
|
||||
* then to get animateLayoutTransition: and didCompleteLayoutTransition: on main.
|
||||
*/
|
||||
- (void)testLayoutTransitionWithSyncMeasurement
|
||||
{
|
||||
[self stubCalculatedLayoutDidChange];
|
||||
|
||||
// Precondition
|
||||
XCTAssertFalse(CGSizeEqualToSize(fixture5.layout.size, fixture1.layout.size));
|
||||
|
||||
// First, apply fixture 5 and run a measurement pass, but don't run a layout pass
|
||||
// After this step, nodes will have pending layouts that are not yet applied
|
||||
[fixture5 apply];
|
||||
[fixture5 withSizeRangesForAllNodesUsingBlock:^(ASLayoutTestNode * _Nonnull node, ASSizeRange sizeRange) {
|
||||
OCMExpect([node.mock calculateLayoutThatFits:sizeRange])
|
||||
.onMainThread();
|
||||
}];
|
||||
|
||||
[nodeA layoutThatFits:ASSizeRangeMake(fixture5.layout.size)];
|
||||
|
||||
// Assert that node A has layout size and size range from fixture 5
|
||||
XCTAssertTrue(CGSizeEqualToSize(fixture5.layout.size, nodeA.calculatedSize));
|
||||
XCTAssertTrue(ASSizeRangeEqualToSizeRange([fixture5 firstSizeRangeForNode:nodeA], nodeA.constrainedSizeForCalculatedLayout));
|
||||
|
||||
// Then switch to fixture 1 and kick off a synchronous layout transition
|
||||
// Unapplied pending layouts from the previous measurement pass will be outdated
|
||||
[fixture1 apply];
|
||||
[fixture1 withSizeRangesForAllNodesUsingBlock:^(ASLayoutTestNode * _Nonnull node, ASSizeRange sizeRange) {
|
||||
OCMExpect([node.mock calculateLayoutThatFits:sizeRange])
|
||||
.onMainThread();
|
||||
}];
|
||||
|
||||
OCMExpect([nodeA.mock animateLayoutTransition:OCMOCK_ANY]).onMainThread();
|
||||
OCMExpect([nodeA.mock didCompleteLayoutTransition:OCMOCK_ANY]).onMainThread();
|
||||
|
||||
[nodeA transitionLayoutWithAnimation:NO shouldMeasureAsync:NO measurementCompletion:nil];
|
||||
|
||||
// Assert that node A picks up new layout size and size range from fixture 1
|
||||
XCTAssertTrue(CGSizeEqualToSize(fixture1.layout.size, nodeA.calculatedSize));
|
||||
XCTAssertTrue(ASSizeRangeEqualToSizeRange([fixture1 firstSizeRangeForNode:nodeA], nodeA.constrainedSizeForCalculatedLayout));
|
||||
|
||||
[window layoutIfNeeded];
|
||||
[self verifyFixture:fixture1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Start at fixture 1.
|
||||
* Trigger an async transition to fixture 2.
|
||||
* While it's measuring, on main switch to fixture 4 (setNeedsLayout A, D) and run a CA layout pass.
|
||||
*
|
||||
* Correct behavior, we end up at fixture 4 since it's newer.
|
||||
* Current incorrect behavior, we end up at fixture 2 and we remeasure surviving node C.
|
||||
* Note: incorrect behavior likely introduced by the early check in __layout added in
|
||||
* https://github.com/facebookarchive/AsyncDisplayKit/pull/2657
|
||||
*/
|
||||
- (void)DISABLE_testASetNeedsLayoutInterferingWithTheCurrentTransition
|
||||
{
|
||||
static BOOL enforceCorrectBehavior = NO;
|
||||
|
||||
[self stubCalculatedLayoutDidChange];
|
||||
[self runFirstLayoutPassWithFixture:fixture1];
|
||||
|
||||
[fixture2 apply];
|
||||
|
||||
// Expect A, C, E to calculate new layouts off-main
|
||||
// dispatch_once onto main to run our injectedMainThread work while the transition calculates.
|
||||
__block dispatch_block_t injectedMainThreadWork = nil;
|
||||
for (ASLayoutTestNode *node in @[ nodeA, nodeC, nodeE ]) {
|
||||
[fixture2 withSizeRangesForNode:node block:^(ASSizeRange sizeRange) {
|
||||
OCMExpect([node.mock calculateLayoutThatFits:sizeRange])
|
||||
.offMainThread()
|
||||
.andDo(^(NSInvocation *inv) {
|
||||
// On first calculateLayoutThatFits, schedule our injected main thread work.
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
injectedMainThreadWork();
|
||||
});
|
||||
});
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
// The code in this section is designed to move in time order, all on the main thread:
|
||||
|
||||
// With the current behavior, the transition will continue and complete.
|
||||
if (!enforceCorrectBehavior) {
|
||||
OCMExpect([nodeA.mock animateLayoutTransition:OCMOCK_ANY]).onMainThread();
|
||||
OCMExpect([nodeA.mock didCompleteLayoutTransition:OCMOCK_ANY]).onMainThread();
|
||||
}
|
||||
|
||||
// Trigger the layout transition.
|
||||
__block dispatch_block_t measurementCompletionBlock = nil;
|
||||
[nodeA transitionLayoutWithAnimation:NO shouldMeasureAsync:YES measurementCompletion:^{
|
||||
measurementCompletionBlock();
|
||||
}];
|
||||
|
||||
// Injected block will get run on main after bg layout calculate starts, but before measurementCompletion
|
||||
__block BOOL injectedMainThreadWorkDone = NO;
|
||||
injectedMainThreadWork = ^{
|
||||
as_log_verbose(OS_LOG_DEFAULT, "Begin injectedMainThreadWork");
|
||||
injectedMainThreadWorkDone = YES;
|
||||
|
||||
[fixture4 apply];
|
||||
as_log_verbose(OS_LOG_DEFAULT, "Did apply new fixture");
|
||||
|
||||
if (enforceCorrectBehavior) {
|
||||
// Correct measurement behavior here is unclear, may depend on whether the layouts which
|
||||
// are common to both fixture2 and fixture4 are available from the cache.
|
||||
} else {
|
||||
// Incorrect behavior: nodeC will get measured against its new bounds on main.
|
||||
const auto cPendingSize = [fixture2 layoutForNode:nodeC].size;
|
||||
OCMExpect([nodeC.mock calculateLayoutThatFits:ASSizeRangeMake(cPendingSize)]).onMainThread();
|
||||
}
|
||||
[window layoutIfNeeded];
|
||||
as_log_verbose(OS_LOG_DEFAULT, "End injectedMainThreadWork");
|
||||
};
|
||||
|
||||
measurementCompletionBlock = ^{
|
||||
XCTAssert(injectedMainThreadWorkDone, @"We hoped to get onto the main thread before the measurementCompletion callback ran.");
|
||||
};
|
||||
|
||||
for (ASLayoutTestNode *node in allNodes) {
|
||||
OCMVerifyAllWithDelay(node.mock, verifyDelay);
|
||||
}
|
||||
|
||||
// Incorrect behavior: The transition will "win" even though its transitioning to stale data.
|
||||
if (enforceCorrectBehavior) {
|
||||
[self verifyFixture:fixture4];
|
||||
} else {
|
||||
[self verifyFixture:fixture2];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start on fixture 3 where nodeB is force-shrunk via multipass layout.
|
||||
* Apply fixture 1, which just changes nodeB's size and calls -setNeedsLayout on it.
|
||||
*
|
||||
* This behavior is currently broken. See implementation for correct behavior and incorrect behavior.
|
||||
*/
|
||||
- (void)testCallingSetNeedsLayoutOnANodeThatWasSubjectToMultipassLayout
|
||||
{
|
||||
static BOOL const enforceCorrectBehavior = NO;
|
||||
[self stubCalculatedLayoutDidChange];
|
||||
[self runFirstLayoutPassWithFixture:fixture3];
|
||||
|
||||
// Switch to fixture 1, updating nodeB's desired size and calling -setNeedsLayout
|
||||
// Now nodeB will fit happily into the stack.
|
||||
[fixture1 apply];
|
||||
|
||||
if (enforceCorrectBehavior) {
|
||||
/*
|
||||
* Correct behavior: nodeB is remeasured against the first (unconstrained) size
|
||||
* and when it's discovered that now nodeB fits, nodeA will re-layout and we'll
|
||||
* end up correctly at fixture1.
|
||||
*/
|
||||
OCMExpect([nodeB.mock calculateLayoutThatFits:[fixture3 firstSizeRangeForNode:nodeB]]);
|
||||
|
||||
[fixture1 withSizeRangesForNode:nodeA block:^(ASSizeRange sizeRange) {
|
||||
OCMExpect([nodeA.mock calculateLayoutThatFits:sizeRange]);
|
||||
}];
|
||||
|
||||
[window layoutIfNeeded];
|
||||
[self verifyFixture:fixture1];
|
||||
} else {
|
||||
/*
|
||||
* Incorrect behavior: nodeB is remeasured against the second (fixed-width) constraint.
|
||||
* The returned value (8) is clamped to the fixed with (7), and then compared to the previous
|
||||
* width (7) and we decide not to propagate up the invalidation, and we stay stuck on the old
|
||||
* layout (fixture3).
|
||||
*/
|
||||
OCMExpect([nodeB.mock calculateLayoutThatFits:nodeB.constrainedSizeForCalculatedLayout]);
|
||||
[window layoutIfNeeded];
|
||||
[self verifyFixture:fixture3];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
- (void)verifyFixture:(ASTLayoutFixture *)fixture
|
||||
{
|
||||
const auto expected = fixture.layout;
|
||||
|
||||
// Ensure expected == frames
|
||||
const auto frames = [fixture.rootNode currentLayoutBasedOnFrames];
|
||||
if (![expected isEqual:frames]) {
|
||||
XCTFail(@"\n*** Layout verification failed – frames don't match expected. ***\nGot:\n%@\nExpected:\n%@", [frames recursiveDescription], [expected recursiveDescription]);
|
||||
}
|
||||
|
||||
// Ensure expected == calculatedLayout
|
||||
const auto calculated = fixture.rootNode.calculatedLayout;
|
||||
if (![expected isEqual:calculated]) {
|
||||
XCTFail(@"\n*** Layout verification failed – calculated layout doesn't match expected. ***\nGot:\n%@\nExpected:\n%@", [calculated recursiveDescription], [expected recursiveDescription]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stubs calculatedLayoutDidChange for all nodes.
|
||||
*
|
||||
* It's not really a core layout engine method, and it's also
|
||||
* currently bugged and gets called a lot so for most
|
||||
* tests its better not to have expectations about it littered around.
|
||||
* https://github.com/TextureGroup/Texture/issues/422
|
||||
*/
|
||||
- (void)stubCalculatedLayoutDidChange
|
||||
{
|
||||
stubbedCalculatedLayoutDidChange = YES;
|
||||
for (ASLayoutTestNode *node in allNodes) {
|
||||
OCMStub([node.mock calculatedLayoutDidChange]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixture 1: A basic horizontal stack, all single-pass.
|
||||
*
|
||||
* [A: HorizStack([B, C, D])]. A is (10x1), B is (1x1), C is (2x1), D is (1x1)
|
||||
*/
|
||||
- (ASTLayoutFixture *)createFixture1
|
||||
{
|
||||
const auto fixture = [[ASTLayoutFixture alloc] init];
|
||||
|
||||
// nodeB
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeB];
|
||||
const auto layoutB = [ASLayout layoutWithLayoutElement:nodeB size:{1,1} position:{0,0} sublayouts:nil];
|
||||
|
||||
// nodeC
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeC];
|
||||
const auto layoutC = [ASLayout layoutWithLayoutElement:nodeC size:{2,1} position:{4,0} sublayouts:nil];
|
||||
|
||||
// nodeD
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeD];
|
||||
const auto layoutD = [ASLayout layoutWithLayoutElement:nodeD size:{1,1} position:{9,0} sublayouts:nil];
|
||||
|
||||
[fixture addSizeRange:{{10, 1}, {10, 1}} forNode:nodeA];
|
||||
const auto layoutA = [ASLayout layoutWithLayoutElement:nodeA size:{10,1} position:ASPointNull sublayouts:@[ layoutB, layoutC, layoutD ]];
|
||||
fixture.layout = layoutA;
|
||||
|
||||
[fixture.layoutSpecBlocks setObject:fixture1and3and5NodeALayoutSpecBlock forKey:nodeA];
|
||||
return fixture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixture 2: A simple transition away from fixture 1.
|
||||
*
|
||||
* [A: HorizStack([B, C, E])]. A is (10x1), B is (1x1), C is (4x1), E is (1x1)
|
||||
*
|
||||
* From fixture 1:
|
||||
* B survives with same layout
|
||||
* C survives with new layout
|
||||
* D is removed
|
||||
* E joins with first layout
|
||||
*/
|
||||
- (ASTLayoutFixture *)createFixture2
|
||||
{
|
||||
const auto fixture = [[ASTLayoutFixture alloc] init];
|
||||
|
||||
// nodeB
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeB];
|
||||
const auto layoutB = [ASLayout layoutWithLayoutElement:nodeB size:{1,1} position:{0,0} sublayouts:nil];
|
||||
|
||||
// nodeC
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeC];
|
||||
const auto layoutC = [ASLayout layoutWithLayoutElement:nodeC size:{4,1} position:{3,0} sublayouts:nil];
|
||||
|
||||
// nodeE
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeE];
|
||||
const auto layoutE = [ASLayout layoutWithLayoutElement:nodeE size:{1,1} position:{9,0} sublayouts:nil];
|
||||
|
||||
[fixture addSizeRange:{{10, 1}, {10, 1}} forNode:nodeA];
|
||||
const auto layoutA = [ASLayout layoutWithLayoutElement:nodeA size:{10,1} position:ASPointNull sublayouts:@[ layoutB, layoutC, layoutE ]];
|
||||
fixture.layout = layoutA;
|
||||
|
||||
ASLayoutSpecBlock specBlockA = ^ASLayoutSpec * _Nonnull(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
return [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:0 justifyContent:ASStackLayoutJustifyContentSpaceBetween alignItems:ASStackLayoutAlignItemsStart children:@[ nodeB, nodeC, nodeE ]];
|
||||
};
|
||||
[fixture.layoutSpecBlocks setObject:specBlockA forKey:nodeA];
|
||||
return fixture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixture 3: Multipass stack layout
|
||||
*
|
||||
* [A: HorizStack([B, C, D])]. A is (10x1), B is (7x1), C is (2x1), D is (1x1)
|
||||
*
|
||||
* nodeB (which has flexShrink=1) will return 8x1 for its size during the first
|
||||
* stack pass, and it'll be subject to a second pass where it returns 7x1.
|
||||
*
|
||||
*/
|
||||
- (ASTLayoutFixture *)createFixture3
|
||||
{
|
||||
const auto fixture = [[ASTLayoutFixture alloc] init];
|
||||
|
||||
// nodeB wants 8,1 but it will settle for 7,1
|
||||
[fixture setReturnedSize:{8,1} forNode:nodeB];
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeB];
|
||||
[fixture addSizeRange:{{7, 0}, {7, 1}} forNode:nodeB];
|
||||
const auto layoutB = [ASLayout layoutWithLayoutElement:nodeB size:{7,1} position:{0,0} sublayouts:nil];
|
||||
|
||||
// nodeC
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeC];
|
||||
const auto layoutC = [ASLayout layoutWithLayoutElement:nodeC size:{2,1} position:{7,0} sublayouts:nil];
|
||||
|
||||
// nodeD
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeD];
|
||||
const auto layoutD = [ASLayout layoutWithLayoutElement:nodeD size:{1,1} position:{9,0} sublayouts:nil];
|
||||
|
||||
[fixture addSizeRange:{{10, 1}, {10, 1}} forNode:nodeA];
|
||||
const auto layoutA = [ASLayout layoutWithLayoutElement:nodeA size:{10,1} position:ASPointNull sublayouts:@[ layoutB, layoutC, layoutD ]];
|
||||
fixture.layout = layoutA;
|
||||
|
||||
[fixture.layoutSpecBlocks setObject:fixture1and3and5NodeALayoutSpecBlock forKey:nodeA];
|
||||
return fixture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixture 4: A different simple transition away from fixture 1.
|
||||
*
|
||||
* [A: HorizStack([B, D, E])]. A is (10x1), B is (1x1), D is (2x1), E is (1x1)
|
||||
*
|
||||
* From fixture 1:
|
||||
* B survives with same layout
|
||||
* C is removed
|
||||
* D survives with new layout
|
||||
* E joins with first layout
|
||||
*
|
||||
* From fixture 2:
|
||||
* B survives with same layout
|
||||
* C is removed
|
||||
* D joins with first layout
|
||||
* E survives with same layout
|
||||
*/
|
||||
- (ASTLayoutFixture *)createFixture4
|
||||
{
|
||||
const auto fixture = [[ASTLayoutFixture alloc] init];
|
||||
|
||||
// nodeB
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeB];
|
||||
const auto layoutB = [ASLayout layoutWithLayoutElement:nodeB size:{1,1} position:{0,0} sublayouts:nil];
|
||||
|
||||
// nodeD
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeD];
|
||||
const auto layoutD = [ASLayout layoutWithLayoutElement:nodeD size:{2,1} position:{4,0} sublayouts:nil];
|
||||
|
||||
// nodeE
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeE];
|
||||
const auto layoutE = [ASLayout layoutWithLayoutElement:nodeE size:{1,1} position:{9,0} sublayouts:nil];
|
||||
|
||||
[fixture addSizeRange:{{10, 1}, {10, 1}} forNode:nodeA];
|
||||
const auto layoutA = [ASLayout layoutWithLayoutElement:nodeA size:{10,1} position:ASPointNull sublayouts:@[ layoutB, layoutD, layoutE ]];
|
||||
fixture.layout = layoutA;
|
||||
|
||||
ASLayoutSpecBlock specBlockA = ^ASLayoutSpec * _Nonnull(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
return [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:0 justifyContent:ASStackLayoutJustifyContentSpaceBetween alignItems:ASStackLayoutAlignItemsStart children:@[ nodeB, nodeD, nodeE ]];
|
||||
};
|
||||
[fixture.layoutSpecBlocks setObject:specBlockA forKey:nodeA];
|
||||
return fixture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixture 5: Same as fixture 1, but with a bigger root node (node A).
|
||||
*
|
||||
* [A: HorizStack([B, C, D])]. A is (15x1), B is (1x1), C is (2x1), D is (1x1)
|
||||
*/
|
||||
- (ASTLayoutFixture *)createFixture5
|
||||
{
|
||||
const auto fixture = [[ASTLayoutFixture alloc] init];
|
||||
|
||||
// nodeB
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeB];
|
||||
const auto layoutB = [ASLayout layoutWithLayoutElement:nodeB size:{1,1} position:{0,0} sublayouts:nil];
|
||||
|
||||
// nodeC
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeC];
|
||||
const auto layoutC = [ASLayout layoutWithLayoutElement:nodeC size:{2,1} position:{4,0} sublayouts:nil];
|
||||
|
||||
// nodeD
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeD];
|
||||
const auto layoutD = [ASLayout layoutWithLayoutElement:nodeD size:{1,1} position:{9,0} sublayouts:nil];
|
||||
|
||||
[fixture addSizeRange:{{15, 1}, {15, 1}} forNode:nodeA];
|
||||
const auto layoutA = [ASLayout layoutWithLayoutElement:nodeA size:{15,1} position:ASPointNull sublayouts:@[ layoutB, layoutC, layoutD ]];
|
||||
fixture.layout = layoutA;
|
||||
|
||||
[fixture.layoutSpecBlocks setObject:fixture1and3and5NodeALayoutSpecBlock forKey:nodeA];
|
||||
return fixture;
|
||||
}
|
||||
|
||||
- (void)runFirstLayoutPassWithFixture:(ASTLayoutFixture *)fixture
|
||||
{
|
||||
[fixture apply];
|
||||
[fixture withSizeRangesForAllNodesUsingBlock:^(ASLayoutTestNode * _Nonnull node, ASSizeRange sizeRange) {
|
||||
OCMExpect([node.mock calculateLayoutThatFits:sizeRange]).onMainThread();
|
||||
|
||||
if (!stubbedCalculatedLayoutDidChange) {
|
||||
OCMExpect([node.mock calculatedLayoutDidChange]).onMainThread();
|
||||
}
|
||||
}];
|
||||
|
||||
// Trigger CA layout pass.
|
||||
[window layoutIfNeeded];
|
||||
|
||||
// Make sure it went through.
|
||||
[self verifyFixture:fixture];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,206 +0,0 @@
|
|||
//
|
||||
// ASLayoutFlatteningTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASLayoutSpec.h>
|
||||
|
||||
@interface ASLayoutFlatteningTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation ASLayoutFlatteningTests
|
||||
|
||||
static ASLayout *layoutWithCustomPosition(CGPoint position, id<ASLayoutElement> element, NSArray<ASLayout *> *sublayouts)
|
||||
{
|
||||
return [ASLayout layoutWithLayoutElement:element
|
||||
size:CGSizeMake(100, 100)
|
||||
position:position
|
||||
sublayouts:sublayouts];
|
||||
}
|
||||
|
||||
static ASLayout *layout(id<ASLayoutElement> element, NSArray<ASLayout *> *sublayouts)
|
||||
{
|
||||
return layoutWithCustomPosition(CGPointZero, element, sublayouts);
|
||||
}
|
||||
|
||||
- (void)testThatFlattenedLayoutContainsOnlyDirectSubnodesInValidOrder
|
||||
{
|
||||
ASLayout *flattenedLayout;
|
||||
|
||||
@autoreleasepool {
|
||||
NSMutableArray<ASDisplayNode *> *subnodes = [NSMutableArray array];
|
||||
NSMutableArray<ASLayoutSpec *> *layoutSpecs = [NSMutableArray array];
|
||||
NSMutableArray<ASDisplayNode *> *indirectSubnodes = [NSMutableArray array];
|
||||
|
||||
ASDisplayNode *(^subnode)(void) = ^ASDisplayNode *() { [subnodes addObject:[[ASDisplayNode alloc] init]]; return [subnodes lastObject]; };
|
||||
ASLayoutSpec *(^layoutSpec)(void) = ^ASLayoutSpec *() { [layoutSpecs addObject:[[ASLayoutSpec alloc] init]]; return [layoutSpecs lastObject]; };
|
||||
ASDisplayNode *(^indirectSubnode)(void) = ^ASDisplayNode *() { [indirectSubnodes addObject:[[ASDisplayNode alloc] init]]; return [indirectSubnodes lastObject]; };
|
||||
|
||||
NSArray<ASLayout *> *sublayouts = @[
|
||||
layout(subnode(), @[
|
||||
layout(indirectSubnode(), @[]),
|
||||
]),
|
||||
layout(layoutSpec(), @[
|
||||
layout(subnode(), @[]),
|
||||
layout(layoutSpec(), @[
|
||||
layout(layoutSpec(), @[]),
|
||||
layout(subnode(), @[]),
|
||||
]),
|
||||
layout(layoutSpec(), @[]),
|
||||
]),
|
||||
layout(layoutSpec(), @[
|
||||
layout(subnode(), @[
|
||||
layout(indirectSubnode(), @[]),
|
||||
layout(indirectSubnode(), @[
|
||||
layout(indirectSubnode(), @[])
|
||||
]),
|
||||
])
|
||||
]),
|
||||
layout(subnode(), @[]),
|
||||
];
|
||||
|
||||
ASDisplayNode *rootNode = [[ASDisplayNode alloc] init];
|
||||
ASLayout *originalLayout = [ASLayout layoutWithLayoutElement:rootNode
|
||||
size:CGSizeMake(1000, 1000)
|
||||
sublayouts:sublayouts];
|
||||
flattenedLayout = [originalLayout filteredNodeLayoutTree];
|
||||
NSArray<ASLayout *> *flattenedSublayouts = flattenedLayout.sublayouts;
|
||||
NSUInteger sublayoutsCount = flattenedSublayouts.count;
|
||||
|
||||
XCTAssertEqualObjects(originalLayout.layoutElement, flattenedLayout.layoutElement, @"The root node should be reserved");
|
||||
XCTAssertTrue(ASPointIsNull(flattenedLayout.position), @"Position of the root layout should be null");
|
||||
XCTAssertEqual(subnodes.count, sublayoutsCount, @"Flattened layout should only contain direct subnodes");
|
||||
for (int i = 0; i < sublayoutsCount; i++) {
|
||||
XCTAssertEqualObjects(subnodes[i], flattenedSublayouts[i].layoutElement, @"Sublayouts should be in correct order (flattened in DFS fashion)");
|
||||
}
|
||||
}
|
||||
|
||||
for (ASLayout *sublayout in flattenedLayout.sublayouts) {
|
||||
XCTAssertNotNil(sublayout.layoutElement, @"Sublayout elements should be retained");
|
||||
XCTAssertEqual(0, sublayout.sublayouts.count, @"Sublayouts should not have their own sublayouts");
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Test reusing ASLayouts while flattening
|
||||
|
||||
- (void)testThatLayoutWithNonNullPositionIsNotReused
|
||||
{
|
||||
ASDisplayNode *rootNode = [[ASDisplayNode alloc] init];
|
||||
ASLayout *originalLayout = layoutWithCustomPosition(CGPointMake(10, 10), rootNode, @[]);
|
||||
ASLayout *flattenedLayout = [originalLayout filteredNodeLayoutTree];
|
||||
XCTAssertNotEqualObjects(originalLayout, flattenedLayout, "@Layout should be reused");
|
||||
XCTAssertTrue(ASPointIsNull(flattenedLayout.position), @"Position of a root layout should be null");
|
||||
}
|
||||
|
||||
- (void)testThatLayoutWithNullPositionAndNoSublayoutIsReused
|
||||
{
|
||||
ASDisplayNode *rootNode = [[ASDisplayNode alloc] init];
|
||||
ASLayout *originalLayout = layoutWithCustomPosition(ASPointNull, rootNode, @[]);
|
||||
ASLayout *flattenedLayout = [originalLayout filteredNodeLayoutTree];
|
||||
XCTAssertEqualObjects(originalLayout, flattenedLayout, "@Layout should be reused");
|
||||
XCTAssertTrue(ASPointIsNull(flattenedLayout.position), @"Position of a root layout should be null");
|
||||
}
|
||||
|
||||
- (void)testThatLayoutWithNullPositionAndFlattenedNodeSublayoutsIsReused
|
||||
{
|
||||
ASLayout *flattenedLayout;
|
||||
|
||||
@autoreleasepool {
|
||||
ASDisplayNode *rootNode = [[ASDisplayNode alloc] init];
|
||||
NSMutableArray<ASDisplayNode *> *subnodes = [NSMutableArray array];
|
||||
ASDisplayNode *(^subnode)(void) = ^ASDisplayNode *() { [subnodes addObject:[[ASDisplayNode alloc] init]]; return [subnodes lastObject]; };
|
||||
ASLayout *originalLayout = layoutWithCustomPosition(ASPointNull,
|
||||
rootNode,
|
||||
@[
|
||||
layoutWithCustomPosition(CGPointMake(10, 10), subnode(), @[]),
|
||||
layoutWithCustomPosition(CGPointMake(20, 20), subnode(), @[]),
|
||||
layoutWithCustomPosition(CGPointMake(30, 30), subnode(), @[]),
|
||||
]);
|
||||
flattenedLayout = [originalLayout filteredNodeLayoutTree];
|
||||
XCTAssertEqualObjects(originalLayout, flattenedLayout, "@Layout should be reused");
|
||||
XCTAssertTrue(ASPointIsNull(flattenedLayout.position), @"Position of the root layout should be null");
|
||||
}
|
||||
|
||||
for (ASLayout *sublayout in flattenedLayout.sublayouts) {
|
||||
XCTAssertNotNil(sublayout.layoutElement, @"Sublayout elements should be retained");
|
||||
XCTAssertEqual(0, sublayout.sublayouts.count, @"Sublayouts should not have their own sublayouts");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testThatLayoutWithNullPositionAndUnflattenedSublayoutsIsNotReused
|
||||
{
|
||||
ASLayout *flattenedLayout;
|
||||
|
||||
@autoreleasepool {
|
||||
ASDisplayNode *rootNode = [[ASDisplayNode alloc] init];
|
||||
NSMutableArray<ASDisplayNode *> *subnodes = [NSMutableArray array];
|
||||
NSMutableArray<ASLayoutSpec *> *layoutSpecs = [NSMutableArray array];
|
||||
NSMutableArray<ASDisplayNode *> *indirectSubnodes = [NSMutableArray array];
|
||||
NSMutableArray<ASLayout *> *reusedLayouts = [NSMutableArray array];
|
||||
|
||||
ASDisplayNode *(^subnode)(void) = ^ASDisplayNode *() { [subnodes addObject:[[ASDisplayNode alloc] init]]; return [subnodes lastObject]; };
|
||||
ASLayoutSpec *(^layoutSpec)(void) = ^ASLayoutSpec *() { [layoutSpecs addObject:[[ASLayoutSpec alloc] init]]; return [layoutSpecs lastObject]; };
|
||||
ASDisplayNode *(^indirectSubnode)(void) = ^ASDisplayNode *() { [indirectSubnodes addObject:[[ASDisplayNode alloc] init]]; return [indirectSubnodes lastObject]; };
|
||||
ASLayout *(^reusedLayout)(ASDisplayNode *) = ^ASLayout *(ASDisplayNode *subnode) { [reusedLayouts addObject:layout(subnode, @[])]; return [reusedLayouts lastObject]; };
|
||||
|
||||
/*
|
||||
* Layouts with sublayouts of both nodes and layout specs should not be reused.
|
||||
* However, all flattened node sublayouts with valid position should be reused.
|
||||
*/
|
||||
ASLayout *originalLayout = layoutWithCustomPosition(ASPointNull,
|
||||
rootNode,
|
||||
@[
|
||||
reusedLayout(subnode()),
|
||||
// The 2 node sublayouts below should be reused although they are in a layout spec sublayout.
|
||||
// That is because each of them have an absolute position of zero.
|
||||
// This case can happen, for example, as the result of a background/overlay layout spec.
|
||||
layout(layoutSpec(), @[
|
||||
reusedLayout(subnode()),
|
||||
reusedLayout(subnode())
|
||||
]),
|
||||
layout(subnode(), @[
|
||||
layout(layoutSpec(), @[])
|
||||
]),
|
||||
layout(subnode(), @[
|
||||
layout(indirectSubnode(), @[])
|
||||
]),
|
||||
layoutWithCustomPosition(CGPointMake(10, 10), subnode(), @[]),
|
||||
// The 2 node sublayouts below shouldn't be reused because they have non-zero absolute positions.
|
||||
layoutWithCustomPosition(CGPointMake(20, 20), layoutSpec(), @[
|
||||
layout(subnode(), @[]),
|
||||
layout(subnode(), @[])
|
||||
]),
|
||||
]);
|
||||
flattenedLayout = [originalLayout filteredNodeLayoutTree];
|
||||
NSArray<ASLayout *> *flattenedSublayouts = flattenedLayout.sublayouts;
|
||||
NSUInteger sublayoutsCount = flattenedSublayouts.count;
|
||||
|
||||
XCTAssertNotEqualObjects(originalLayout, flattenedLayout, @"Original layout should not be reused");
|
||||
XCTAssertEqualObjects(originalLayout.layoutElement, flattenedLayout.layoutElement, @"The root node should be reserved");
|
||||
XCTAssertTrue(ASPointIsNull(flattenedLayout.position), @"Position of the root layout should be null");
|
||||
XCTAssertTrue(reusedLayouts.count <= sublayoutsCount, @"Some sublayouts can't be reused");
|
||||
XCTAssertEqual(subnodes.count, sublayoutsCount, @"Flattened layout should only contain direct subnodes");
|
||||
int numOfActualReusedLayouts = 0;
|
||||
for (int i = 0; i < sublayoutsCount; i++) {
|
||||
ASLayout *sublayout = flattenedSublayouts[i];
|
||||
XCTAssertEqualObjects(subnodes[i], sublayout.layoutElement, @"Sublayouts should be in correct order (flattened in DFS fashion)");
|
||||
if ([reusedLayouts containsObject:sublayout]) {
|
||||
numOfActualReusedLayouts++;
|
||||
}
|
||||
}
|
||||
XCTAssertEqual(numOfActualReusedLayouts, reusedLayouts.count, @"Should reuse all layouts that can be reused");
|
||||
}
|
||||
|
||||
for (ASLayout *sublayout in flattenedLayout.sublayouts) {
|
||||
XCTAssertNotNil(sublayout.layoutElement, @"Sublayout elements should be retained");
|
||||
XCTAssertEqual(0, sublayout.sublayouts.count, @"Sublayouts should not have their own sublayouts");
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
//
|
||||
// ASLayoutSpecSnapshotTestsHelper.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASSnapshotTestCase.h"
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
|
||||
@class ASLayoutSpec;
|
||||
|
||||
@interface ASLayoutSpecSnapshotTestCase: ASSnapshotTestCase
|
||||
/**
|
||||
Test the layout spec or records a snapshot if recordMode is YES.
|
||||
@param layoutSpec The layout spec under test or to snapshot
|
||||
@param sizeRange The size range used to calculate layout of the given layout spec.
|
||||
@param subnodes An array of ASDisplayNodes used within the layout spec.
|
||||
@param identifier An optional identifier, used to identify this snapshot test.
|
||||
|
||||
@discussion In order to make the layout spec visible, it is embeded to a ASDisplayNode host.
|
||||
Any subnodes used within the layout spec must be provided.
|
||||
They will be added to the host in the same order as the array.
|
||||
*/
|
||||
- (void)testLayoutSpec:(ASLayoutSpec *)layoutSpec
|
||||
sizeRange:(ASSizeRange)sizeRange
|
||||
subnodes:(NSArray *)subnodes
|
||||
identifier:(NSString *)identifier;
|
||||
@end
|
||||
|
||||
__attribute__((overloadable)) static inline ASDisplayNode *ASDisplayNodeWithBackgroundColor(UIColor *backgroundColor, CGSize size) {
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||
node.layerBacked = YES;
|
||||
node.backgroundColor = backgroundColor;
|
||||
node.style.width = ASDimensionMakeWithPoints(size.width);
|
||||
node.style.height = ASDimensionMakeWithPoints(size.height);
|
||||
return node;
|
||||
}
|
||||
|
||||
__attribute__((overloadable)) static inline ASDisplayNode *ASDisplayNodeWithBackgroundColor(UIColor *backgroundColor)
|
||||
{
|
||||
return ASDisplayNodeWithBackgroundColor(backgroundColor, CGSizeZero);
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
//
|
||||
// ASLayoutSpecSnapshotTestsHelper.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutSpecSnapshotTestsHelper.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
#import <AsyncDisplayKit/ASLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
|
||||
@interface ASTestNode : ASDisplayNode
|
||||
@property (nonatomic, nullable) ASLayoutSpec *layoutSpecUnderTest;
|
||||
@end
|
||||
|
||||
@implementation ASLayoutSpecSnapshotTestCase
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
self.recordMode = NO;
|
||||
}
|
||||
|
||||
- (void)testLayoutSpec:(ASLayoutSpec *)layoutSpec
|
||||
sizeRange:(ASSizeRange)sizeRange
|
||||
subnodes:(NSArray *)subnodes
|
||||
identifier:(NSString *)identifier
|
||||
{
|
||||
ASTestNode *node = [[ASTestNode alloc] init];
|
||||
|
||||
for (ASDisplayNode *subnode in subnodes) {
|
||||
[node addSubnode:subnode];
|
||||
}
|
||||
|
||||
node.layoutSpecUnderTest = layoutSpec;
|
||||
|
||||
ASDisplayNodeSizeToFitSizeRange(node, sizeRange);
|
||||
ASSnapshotVerifyNode(node, identifier);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTestNode
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
self.layerBacked = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
|
||||
{
|
||||
return _layoutSpecUnderTest;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
//
|
||||
// ASLayoutSpecTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <AsyncDisplayKit/ASLayoutElementExtensibility.h>
|
||||
|
||||
#pragma mark - ASDKExtendedLayoutSpec
|
||||
|
||||
/*
|
||||
* Extend the ASDKExtendedLayoutElement
|
||||
* It adds a
|
||||
* - primitive / CGFloat (extendedWidth)
|
||||
* - struct / ASDimension (extendedDimension)
|
||||
* - primitive / ASStackLayoutDirection (extendedDirection)
|
||||
*/
|
||||
@protocol ASDKExtendedLayoutElement <NSObject>
|
||||
@property (nonatomic) CGFloat extendedWidth;
|
||||
@property (nonatomic) ASDimension extendedDimension;
|
||||
@property (copy, nonatomic) NSString *extendedName;
|
||||
@end
|
||||
|
||||
/*
|
||||
* Let the ASLayoutElementStyle conform to the ASDKExtendedLayoutElement protocol and add properties implementation
|
||||
*/
|
||||
@interface ASLayoutElementStyle (ASDKExtendedLayoutElement) <ASDKExtendedLayoutElement>
|
||||
@end
|
||||
|
||||
@implementation ASLayoutElementStyle (ASDKExtendedLayoutElement)
|
||||
ASDK_STYLE_PROP_PRIM(CGFloat, extendedWidth, setExtendedWidth, 0);
|
||||
ASDK_STYLE_PROP_STR(ASDimension, extendedDimension, setExtendedDimension, ASDimensionMake(ASDimensionUnitAuto, 0));
|
||||
ASDK_STYLE_PROP_OBJ(NSString *, extendedName, setExtendedName);
|
||||
@end
|
||||
|
||||
/*
|
||||
* As the ASLayoutElementStyle conforms to the ASDKExtendedLayoutElement protocol now, ASDKExtendedLayoutElement properties
|
||||
* can be accessed in ASDKExtendedLayoutSpec
|
||||
*/
|
||||
@interface ASDKExtendedLayoutSpec : ASLayoutSpec
|
||||
@end
|
||||
|
||||
@implementation ASDKExtendedLayoutSpec
|
||||
|
||||
- (void)doSetSomeStyleValuesToChildren
|
||||
{
|
||||
for (id<ASLayoutElement> child in self.children) {
|
||||
child.style.extendedWidth = 100;
|
||||
child.style.extendedDimension = ASDimensionMake(100);
|
||||
child.style.extendedName = @"ASDK";
|
||||
}
|
||||
}
|
||||
|
||||
- (void)doUseSomeStyleValuesFromChildren
|
||||
{
|
||||
for (id<ASLayoutElement> child in self.children) {
|
||||
__unused CGFloat extendedWidth = child.style.extendedWidth;
|
||||
__unused ASDimension extendedDimension = child.style.extendedDimension;
|
||||
__unused NSString *extendedName = child.style.extendedName;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark - ASLayoutSpecTests
|
||||
|
||||
@interface ASLayoutSpecTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASLayoutSpecTests
|
||||
|
||||
- (void)testSetPrimitiveToExtendedStyle
|
||||
{
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||
node.style.extendedWidth = 100;
|
||||
XCTAssert(node.style.extendedWidth == 100, @"Primitive value should be set on extended style");
|
||||
}
|
||||
|
||||
- (void)testSetStructToExtendedStyle
|
||||
{
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||
node.style.extendedDimension = ASDimensionMake(100);
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(node.style.extendedDimension, ASDimensionMake(100)), @"Struct should be set on extended style");
|
||||
}
|
||||
|
||||
- (void)testSetObjectToExtendedStyle
|
||||
{
|
||||
NSString *extendedName = @"ASDK";
|
||||
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||
node.style.extendedName = extendedName;
|
||||
XCTAssertEqualObjects(node.style.extendedName, extendedName, @"Object should be set on extended style");
|
||||
}
|
||||
|
||||
|
||||
- (void)testUseOfExtendedStyleProperties
|
||||
{
|
||||
ASDKExtendedLayoutSpec *extendedLayoutSpec = [ASDKExtendedLayoutSpec new];
|
||||
extendedLayoutSpec.children = @[[[ASDisplayNode alloc] init], [[ASDisplayNode alloc] init]];
|
||||
XCTAssertNoThrow([extendedLayoutSpec doSetSomeStyleValuesToChildren]);
|
||||
XCTAssertNoThrow([extendedLayoutSpec doUseSomeStyleValuesFromChildren]);
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
//
|
||||
// ASLayoutTestNode.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <OCMock/OCMock.h>
|
||||
|
||||
@interface ASLayoutTestNode : ASDisplayNode
|
||||
|
||||
/**
|
||||
* Mocking ASDisplayNodes directly isn't very safe because when you pump mock objects
|
||||
* into the guts of the framework, bad things happen e.g. direct-ivar-access on mock
|
||||
* objects will return garbage data.
|
||||
*
|
||||
* Instead we create a strict mock for each node, and forward a selected set of calls to it.
|
||||
*/
|
||||
@property (nonatomic, readonly) id mock;
|
||||
|
||||
/**
|
||||
* The size that this node will return in calculateLayoutThatFits (if it doesn't have a layoutSpecBlock).
|
||||
*
|
||||
* Changing this value will call -setNeedsLayout on the node.
|
||||
*/
|
||||
@property (nonatomic) CGSize testSize;
|
||||
|
||||
/**
|
||||
* Generate a layout based on the frame of this node and its subtree.
|
||||
*
|
||||
* The root layout will be unpositioned. This is so that the returned layout can be directly
|
||||
* compared to `calculatedLayout`
|
||||
*/
|
||||
- (ASLayout *)currentLayoutBasedOnFrames;
|
||||
|
||||
@end
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
//
|
||||
// ASLayoutTestNode.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutTestNode.h"
|
||||
#import <OCMock/OCMock.h>
|
||||
#import "OCMockObject+ASAdditions.h"
|
||||
|
||||
@implementation ASLayoutTestNode
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_mock = OCMStrictClassMock([ASDisplayNode class]);
|
||||
|
||||
// If errors occur (e.g. unexpected method) we need to quickly figure out
|
||||
// which node is at fault, so we inject the node name into the mock instance
|
||||
// description.
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
[_mock setModifyDescriptionBlock:^(id mock, NSString *baseDescription){
|
||||
return [NSString stringWithFormat:@"Mock(%@)", weakSelf.description];
|
||||
}];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ASLayout *)currentLayoutBasedOnFrames
|
||||
{
|
||||
return [self _currentLayoutBasedOnFramesForRootNode:YES];
|
||||
}
|
||||
|
||||
- (ASLayout *)_currentLayoutBasedOnFramesForRootNode:(BOOL)isRootNode
|
||||
{
|
||||
const auto sublayouts = [[NSMutableArray<ASLayout *> alloc] init];
|
||||
for (ASLayoutTestNode *subnode in self.subnodes) {
|
||||
[sublayouts addObject:[subnode _currentLayoutBasedOnFramesForRootNode:NO]];
|
||||
}
|
||||
CGPoint rootPosition = isRootNode ? ASPointNull : self.frame.origin;
|
||||
return [ASLayout layoutWithLayoutElement:self size:self.frame.size position:rootPosition sublayouts:sublayouts];
|
||||
}
|
||||
|
||||
- (void)setTestSize:(CGSize)testSize
|
||||
{
|
||||
if (!CGSizeEqualToSize(testSize, _testSize)) {
|
||||
_testSize = testSize;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}
|
||||
|
||||
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize
|
||||
{
|
||||
[_mock calculateLayoutThatFits:constrainedSize];
|
||||
|
||||
// If we have a layout spec block, or no test size, return super.
|
||||
if (self.layoutSpecBlock || CGSizeEqualToSize(self.testSize, CGSizeZero)) {
|
||||
return [super calculateLayoutThatFits:constrainedSize];
|
||||
} else {
|
||||
// Interestingly, the infra will auto-clamp sizes from calculateSizeThatFits, but not from calculateLayoutThatFits.
|
||||
const auto size = ASSizeRangeClamp(constrainedSize, self.testSize);
|
||||
return [ASLayout layoutWithLayoutElement:self size:size];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Forwarding to mock
|
||||
|
||||
- (void)calculatedLayoutDidChange
|
||||
{
|
||||
[_mock calculatedLayoutDidChange];
|
||||
[super calculatedLayoutDidChange];
|
||||
}
|
||||
|
||||
- (void)didCompleteLayoutTransition:(id<ASContextTransitioning>)context
|
||||
{
|
||||
[_mock didCompleteLayoutTransition:context];
|
||||
[super didCompleteLayoutTransition:context];
|
||||
}
|
||||
|
||||
- (void)animateLayoutTransition:(id<ASContextTransitioning>)context
|
||||
{
|
||||
[_mock animateLayoutTransition:context];
|
||||
[super animateLayoutTransition:context];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,265 +0,0 @@
|
|||
//
|
||||
// ASMultiplexImageNodeTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <OCMock/OCMock.h>
|
||||
#import "NSInvocation+ASTestHelpers.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASImageProtocols.h>
|
||||
#import <AsyncDisplayKit/ASMultiplexImageNode.h>
|
||||
#import <AsyncDisplayKit/ASImageContainerProtocolCategories.h>
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
@interface ASMultiplexImageNodeTests : XCTestCase
|
||||
{
|
||||
@private
|
||||
id mockCache;
|
||||
id mockDownloader;
|
||||
id mockDataSource;
|
||||
id mockDelegate;
|
||||
ASMultiplexImageNode *imageNode;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASMultiplexImageNodeTests
|
||||
|
||||
#pragma mark - Helpers.
|
||||
|
||||
- (NSURL *)_testImageURL
|
||||
{
|
||||
return [[NSBundle bundleForClass:[self class]] URLForResource:@"logo-square"
|
||||
withExtension:@"png"
|
||||
subdirectory:@"TestResources"];
|
||||
}
|
||||
|
||||
- (UIImage *)_testImage
|
||||
{
|
||||
return [UIImage imageWithContentsOfFile:[self _testImageURL].path];
|
||||
}
|
||||
|
||||
#pragma mark - Unit tests.
|
||||
|
||||
// TODO: add tests for delegate display notifications
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
|
||||
mockCache = OCMStrictProtocolMock(@protocol(ASImageCacheProtocol));
|
||||
[mockCache setExpectationOrderMatters:YES];
|
||||
mockDownloader = OCMStrictProtocolMock(@protocol(ASImageDownloaderProtocol));
|
||||
[mockDownloader setExpectationOrderMatters:YES];
|
||||
imageNode = [[ASMultiplexImageNode alloc] initWithCache:mockCache downloader:mockDownloader];
|
||||
|
||||
mockDataSource = OCMStrictProtocolMock(@protocol(ASMultiplexImageNodeDataSource));
|
||||
[mockDataSource setExpectationOrderMatters:YES];
|
||||
imageNode.dataSource = mockDataSource;
|
||||
|
||||
mockDelegate = OCMProtocolMock(@protocol(ASMultiplexImageNodeDelegate));
|
||||
[mockDelegate setExpectationOrderMatters:YES];
|
||||
imageNode.delegate = mockDelegate;
|
||||
}
|
||||
|
||||
- (void)tearDown
|
||||
{
|
||||
OCMVerifyAll(mockDelegate);
|
||||
OCMVerifyAll(mockDataSource);
|
||||
OCMVerifyAll(mockDownloader);
|
||||
OCMVerifyAll(mockCache);
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
- (void)testDataSourceImageMethod
|
||||
{
|
||||
NSNumber *imageIdentifier = @1;
|
||||
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:imageIdentifier])
|
||||
.andReturn([self _testImage]);
|
||||
|
||||
imageNode.imageIdentifiers = @[imageIdentifier];
|
||||
[imageNode reloadImageIdentifierSources];
|
||||
|
||||
// Also expect it to be loaded immediately.
|
||||
XCTAssertEqualObjects(imageNode.loadedImageIdentifier, imageIdentifier, @"imageIdentifier was not loaded");
|
||||
// And for the image to be equivalent to the image we provided.
|
||||
XCTAssertEqualObjects(UIImagePNGRepresentation(imageNode.image),
|
||||
UIImagePNGRepresentation([self _testImage]),
|
||||
@"Loaded image isn't the one we provided");
|
||||
}
|
||||
|
||||
- (void)testDataSourceURLMethod
|
||||
{
|
||||
NSNumber *imageIdentifier = @1;
|
||||
|
||||
// First expect to be hit for the image directly, and fail to return it.
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:imageIdentifier])
|
||||
.andReturn((id)nil);
|
||||
// BUG: -imageForImageIdentifier is called twice in this case (where we return nil).
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:imageIdentifier])
|
||||
.andReturn((id)nil);
|
||||
// Then expect to be hit for the URL, which we'll return.
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode URLForImageIdentifier:imageIdentifier])
|
||||
.andReturn([self _testImageURL]);
|
||||
|
||||
// Mock the cache to do a cache-hit for the test image URL.
|
||||
OCMExpect([mockCache cachedImageWithURL:[self _testImageURL] callbackQueue:OCMOCK_ANY completion:[OCMArg isNotNil]])
|
||||
.andDo(^(NSInvocation *inv) {
|
||||
ASImageCacherCompletion completionBlock = [inv as_argumentAtIndexAsObject:4];
|
||||
completionBlock([self _testImage]);
|
||||
});
|
||||
|
||||
imageNode.imageIdentifiers = @[imageIdentifier];
|
||||
// Kick off loading.
|
||||
[imageNode reloadImageIdentifierSources];
|
||||
|
||||
// Also expect it to be loaded immediately.
|
||||
XCTAssertEqualObjects(imageNode.loadedImageIdentifier, imageIdentifier, @"imageIdentifier was not loaded");
|
||||
// And for the image to be equivalent to the image we provided.
|
||||
XCTAssertEqualObjects(UIImagePNGRepresentation(imageNode.image),
|
||||
UIImagePNGRepresentation([self _testImage]),
|
||||
@"Loaded image isn't the one we provided");
|
||||
}
|
||||
|
||||
- (void)testAddLowerQualityImageIdentifier
|
||||
{
|
||||
// Adding a lower quality image identifier should not cause any loading.
|
||||
NSNumber *highResIdentifier = @2, *lowResIdentifier = @1;
|
||||
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:highResIdentifier])
|
||||
.andReturn([self _testImage]);
|
||||
imageNode.imageIdentifiers = @[highResIdentifier];
|
||||
[imageNode reloadImageIdentifierSources];
|
||||
|
||||
// At this point, we should have the high-res identifier loaded and the DS should have been hit once.
|
||||
XCTAssertEqualObjects(imageNode.loadedImageIdentifier, highResIdentifier, @"High res identifier should be loaded.");
|
||||
|
||||
// BUG: We should not get another -imageForImageIdentifier:highResIdentifier.
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:highResIdentifier])
|
||||
.andReturn([self _testImage]);
|
||||
|
||||
imageNode.imageIdentifiers = @[highResIdentifier, lowResIdentifier];
|
||||
[imageNode reloadImageIdentifierSources];
|
||||
|
||||
// At this point the high-res should still be loaded, and the data source should not have been hit again (see BUG above).
|
||||
XCTAssertEqualObjects(imageNode.loadedImageIdentifier, highResIdentifier, @"High res identifier should be loaded.");
|
||||
}
|
||||
|
||||
- (void)testAddHigherQualityImageIdentifier
|
||||
{
|
||||
NSNumber *lowResIdentifier = @1, *highResIdentifier = @2;
|
||||
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:lowResIdentifier])
|
||||
.andReturn([self _testImage]);
|
||||
|
||||
imageNode.imageIdentifiers = @[lowResIdentifier];
|
||||
[imageNode reloadImageIdentifierSources];
|
||||
|
||||
// At this point, we should have the low-res identifier loaded and the DS should have been hit once.
|
||||
XCTAssertEqualObjects(imageNode.loadedImageIdentifier, lowResIdentifier, @"Low res identifier should be loaded.");
|
||||
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:highResIdentifier])
|
||||
.andReturn([self _testImage]);
|
||||
|
||||
imageNode.imageIdentifiers = @[highResIdentifier, lowResIdentifier];
|
||||
[imageNode reloadImageIdentifierSources];
|
||||
|
||||
// At this point the high-res should be loaded, and the data source should been hit twice.
|
||||
XCTAssertEqualObjects(imageNode.loadedImageIdentifier, highResIdentifier, @"High res identifier should be loaded.");
|
||||
}
|
||||
|
||||
- (void)testIntermediateImageDownloading
|
||||
{
|
||||
imageNode.downloadsIntermediateImages = YES;
|
||||
|
||||
// Let them call URLForImageIdentifier all they want.
|
||||
OCMStub([mockDataSource multiplexImageNode:imageNode URLForImageIdentifier:[OCMArg isNotNil]]);
|
||||
|
||||
// Set up a few identifiers to load.
|
||||
NSInteger identifierCount = 5;
|
||||
NSMutableArray *imageIdentifiers = [NSMutableArray array];
|
||||
for (NSInteger identifier = identifierCount; identifier > 0; identifier--) {
|
||||
[imageIdentifiers addObject:@(identifier)];
|
||||
}
|
||||
|
||||
// Create the array of IDs in the order we expect them to get -imageForImageIdentifier:
|
||||
// BUG: The second to last ID (the last one that returns nil) will get -imageForImageIdentifier: called
|
||||
// again after the last ID (the one that returns non-nil).
|
||||
id secondToLastID = imageIdentifiers[identifierCount - 2];
|
||||
NSArray *imageIdentifiersThatWillBeCalled = [imageIdentifiers arrayByAddingObject:secondToLastID];
|
||||
|
||||
for (id imageID in imageIdentifiersThatWillBeCalled) {
|
||||
// Return nil for everything except the worst ID.
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:imageID])
|
||||
.andDo(^(NSInvocation *inv){
|
||||
id imageID = [inv as_argumentAtIndexAsObject:3];
|
||||
if ([imageID isEqual:imageIdentifiers.lastObject]) {
|
||||
[inv as_setReturnValueWithObject:[self _testImage]];
|
||||
} else {
|
||||
[inv as_setReturnValueWithObject:nil];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
imageNode.imageIdentifiers = imageIdentifiers;
|
||||
[imageNode reloadImageIdentifierSources];
|
||||
}
|
||||
|
||||
- (void)testUncachedDownload
|
||||
{
|
||||
// Mock a cache miss.
|
||||
OCMExpect([mockCache cachedImageWithURL:[self _testImageURL] callbackQueue:OCMOCK_ANY completion:[OCMArg isNotNil]])
|
||||
.andDo(^(NSInvocation *inv){
|
||||
ASImageCacherCompletion completion = [inv as_argumentAtIndexAsObject:4];
|
||||
completion(nil);
|
||||
});
|
||||
|
||||
// Mock a 50%-progress URL download.
|
||||
const CGFloat mockedProgress = 0.5;
|
||||
OCMExpect([mockDownloader downloadImageWithURL:[self _testImageURL] callbackQueue:OCMOCK_ANY downloadProgress:[OCMArg isNotNil] completion:[OCMArg isNotNil]])
|
||||
.andDo(^(NSInvocation *inv){
|
||||
// Simulate progress.
|
||||
ASImageDownloaderProgress progressBlock = [inv as_argumentAtIndexAsObject:4];
|
||||
progressBlock(mockedProgress);
|
||||
|
||||
// Simulate completion.
|
||||
ASImageDownloaderCompletion completionBlock = [inv as_argumentAtIndexAsObject:5];
|
||||
completionBlock([self _testImage], nil, nil, nil);
|
||||
});
|
||||
|
||||
NSNumber *imageIdentifier = @1;
|
||||
|
||||
// Mock the data source to return nil image, and our test URL.
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:imageIdentifier]);
|
||||
// BUG: Multiplex image node will call imageForImageIdentifier twice if we return nil.
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:imageIdentifier]);
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode URLForImageIdentifier:imageIdentifier])
|
||||
.andReturn([self _testImageURL]);
|
||||
|
||||
// Mock the delegate to expect start, 50% progress, and completion invocations.
|
||||
OCMExpect([mockDelegate multiplexImageNode:imageNode didStartDownloadOfImageWithIdentifier:imageIdentifier]);
|
||||
OCMExpect([mockDelegate multiplexImageNode:imageNode didUpdateDownloadProgress:mockedProgress forImageWithIdentifier:imageIdentifier]);
|
||||
OCMExpect([mockDelegate multiplexImageNode:imageNode didUpdateImage:[OCMArg isNotNil] withIdentifier:imageIdentifier fromImage:[OCMArg isNil] withIdentifier:[OCMArg isNil]]);
|
||||
OCMExpect([mockDelegate multiplexImageNode:imageNode didFinishDownloadingImageWithIdentifier:imageIdentifier error:[OCMArg isNil]]);
|
||||
|
||||
imageNode.imageIdentifiers = @[imageIdentifier];
|
||||
// Kick off loading.
|
||||
[imageNode reloadImageIdentifierSources];
|
||||
|
||||
// Wait until the image is loaded.
|
||||
[self expectationForPredicate:[NSPredicate predicateWithFormat:@"loadedImageIdentifier = %@", imageIdentifier] evaluatedWithObject:imageNode handler:nil];
|
||||
[self waitForExpectationsWithTimeout:30 handler:nil];
|
||||
}
|
||||
|
||||
- (void)testThatSettingAnImageExternallyWillThrow
|
||||
{
|
||||
XCTAssertThrows(imageNode.image = [UIImage imageNamed:@""]);
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
//
|
||||
// ASMutableAttributedStringBuilderTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASMutableAttributedStringBuilder.h>
|
||||
|
||||
@interface ASMutableAttributedStringBuilderTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASMutableAttributedStringBuilderTests
|
||||
|
||||
- (NSString *)_string
|
||||
{
|
||||
return @"Normcore PBR hella, viral slow-carb mustache chillwave church-key cornhole messenger bag swag vinyl biodiesel ethnic. Fashion axe messenger bag raw denim street art. Flannel Wes Anderson normcore church-key 8-bit. Master cleanse four loko try-hard Carles stumptown ennui, twee literally wayfarers kitsch tofu PBR. Cliche organic post-ironic Wes Anderson kale chips fashion axe. Narwhal Blue Bottle sustainable, Odd Future Godard sriracha banjo disrupt Marfa irony pug Wes Anderson YOLO yr church-key. Mlkshk Intelligentsia semiotics quinoa, butcher meggings wolf Bushwick keffiyeh ethnic pour-over Pinterest letterpress.";
|
||||
}
|
||||
|
||||
- (ASMutableAttributedStringBuilder *)_builder
|
||||
{
|
||||
return [[ASMutableAttributedStringBuilder alloc] initWithString:[self _string]];
|
||||
}
|
||||
|
||||
- (NSRange)_randomizedRangeForStringBuilder:(ASMutableAttributedStringBuilder *)builder
|
||||
{
|
||||
NSUInteger loc = arc4random() % (builder.length - 1);
|
||||
NSUInteger len = arc4random() % (builder.length - loc);
|
||||
len = ((len > 0) ? len : 1);
|
||||
return NSMakeRange(loc, len);
|
||||
}
|
||||
|
||||
- (void)testSimpleAttributions
|
||||
{
|
||||
// Add a attributes, and verify that they get set on the correct locations.
|
||||
for (int i = 0; i < 100; i++) {
|
||||
ASMutableAttributedStringBuilder *builder = [self _builder];
|
||||
NSRange range = [self _randomizedRangeForStringBuilder:builder];
|
||||
NSString *keyValue = [NSString stringWithFormat:@"%d", i];
|
||||
[builder addAttribute:keyValue value:keyValue range:range];
|
||||
NSAttributedString *attrStr = [builder composedAttributedString];
|
||||
XCTAssertEqual(builder.length, attrStr.length, @"out string should have same length as builder");
|
||||
__block BOOL found = NO;
|
||||
[attrStr enumerateAttributesInRange:NSMakeRange(0, attrStr.length) options:0 usingBlock:^(NSDictionary *attrs, NSRange r, BOOL *stop) {
|
||||
if ([attrs[keyValue] isEqualToString:keyValue]) {
|
||||
XCTAssertTrue(NSEqualRanges(range, r), @"enumerated range %@ should be equal to the set range %@", NSStringFromRange(r), NSStringFromRange(range));
|
||||
found = YES;
|
||||
}
|
||||
}];
|
||||
XCTAssertTrue(found, @"enumeration should have found the attribute we set");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testSetOverAdd
|
||||
{
|
||||
ASMutableAttributedStringBuilder *builder = [self _builder];
|
||||
NSRange addRange = NSMakeRange(0, builder.length);
|
||||
NSRange setRange = NSMakeRange(0, 1);
|
||||
[builder addAttribute:@"attr" value:@"val1" range:addRange];
|
||||
[builder setAttributes:@{@"attr" : @"val2"} range:setRange];
|
||||
NSAttributedString *attrStr = [builder composedAttributedString];
|
||||
NSRange setRangeOut;
|
||||
NSString *setAttr = [attrStr attribute:@"attr" atIndex:0 effectiveRange:&setRangeOut];
|
||||
XCTAssertTrue(NSEqualRanges(setRange, setRangeOut), @"The out set range should equal the range we used originally");
|
||||
XCTAssertEqualObjects(setAttr, @"val2", @"the set value should be val2");
|
||||
|
||||
NSRange addRangeOut;
|
||||
NSString *addAttr = [attrStr attribute:@"attr" atIndex:2 effectiveRange:&addRangeOut];
|
||||
XCTAssertTrue(NSEqualRanges(NSMakeRange(1, builder.length - 1), addRangeOut), @"the add range should only cover beyond the set range");
|
||||
XCTAssertEqualObjects(addAttr, @"val1", @"the added attribute should be present at index 2");
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
//
|
||||
// ASNavigationControllerTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
@interface ASNavigationControllerTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation ASNavigationControllerTests
|
||||
|
||||
- (void)testSetViewControllers {
|
||||
ASViewController *firstController = [ASViewController new];
|
||||
ASViewController *secondController = [ASViewController new];
|
||||
NSArray *expectedViewControllerStack = @[firstController, secondController];
|
||||
ASNavigationController *navigationController = [ASNavigationController new];
|
||||
[navigationController setViewControllers:@[firstController, secondController]];
|
||||
XCTAssertEqual(navigationController.topViewController, secondController);
|
||||
XCTAssertEqual(navigationController.visibleViewController, secondController);
|
||||
XCTAssertTrue([navigationController.viewControllers isEqualToArray:expectedViewControllerStack]);
|
||||
}
|
||||
|
||||
- (void)testPopViewController {
|
||||
ASViewController *firstController = [ASViewController new];
|
||||
ASViewController *secondController = [ASViewController new];
|
||||
NSArray *expectedViewControllerStack = @[firstController];
|
||||
ASNavigationController *navigationController = [ASNavigationController new];
|
||||
[navigationController setViewControllers:@[firstController, secondController]];
|
||||
[navigationController popViewControllerAnimated:false];
|
||||
XCTAssertEqual(navigationController.topViewController, firstController);
|
||||
XCTAssertEqual(navigationController.visibleViewController, firstController);
|
||||
XCTAssertTrue([navigationController.viewControllers isEqualToArray:expectedViewControllerStack]);
|
||||
}
|
||||
|
||||
- (void)testPushViewController {
|
||||
ASViewController *firstController = [ASViewController new];
|
||||
ASViewController *secondController = [ASViewController new];
|
||||
NSArray *expectedViewControllerStack = @[firstController, secondController];
|
||||
ASNavigationController *navigationController = [[ASNavigationController new] initWithRootViewController:firstController];
|
||||
[navigationController pushViewController:secondController animated:false];
|
||||
XCTAssertEqual(navigationController.topViewController, secondController);
|
||||
XCTAssertEqual(navigationController.visibleViewController, secondController);
|
||||
XCTAssertTrue([navigationController.viewControllers isEqualToArray:expectedViewControllerStack]);
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
//
|
||||
// ASNetworkImageNodeTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <OCMock/OCMock.h>
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
|
||||
@interface ASNetworkImageNodeTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@interface ASTestImageDownloader : NSObject <ASImageDownloaderProtocol>
|
||||
@end
|
||||
@interface ASTestImageCache : NSObject <ASImageCacheProtocol>
|
||||
@end
|
||||
|
||||
@implementation ASNetworkImageNodeTests {
|
||||
ASNetworkImageNode *node;
|
||||
id downloader;
|
||||
id cache;
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
cache = [OCMockObject partialMockForObject:[[ASTestImageCache alloc] init]];
|
||||
downloader = [OCMockObject partialMockForObject:[[ASTestImageDownloader alloc] init]];
|
||||
node = [[ASNetworkImageNode alloc] initWithCache:cache downloader:downloader];
|
||||
}
|
||||
|
||||
/// Test is flaky: https://github.com/facebook/AsyncDisplayKit/issues/2898
|
||||
- (void)DISABLED_testThatProgressBlockIsSetAndClearedCorrectlyOnVisibility
|
||||
{
|
||||
node.URL = [NSURL URLWithString:@"http://imageA"];
|
||||
|
||||
// Enter preload range, wait for download start.
|
||||
[[[downloader expect] andForwardToRealObject] downloadImageWithURL:[OCMArg isNotNil] callbackQueue:OCMOCK_ANY downloadProgress:OCMOCK_ANY completion:OCMOCK_ANY];
|
||||
[node enterInterfaceState:ASInterfaceStatePreload];
|
||||
[downloader verifyWithDelay:5];
|
||||
|
||||
// Make the node visible.
|
||||
[[downloader expect] setProgressImageBlock:[OCMArg isNotNil] callbackQueue:OCMOCK_ANY withDownloadIdentifier:@0];
|
||||
[node enterInterfaceState:ASInterfaceStateInHierarchy];
|
||||
[downloader verify];
|
||||
|
||||
// Make the node invisible.
|
||||
[[downloader expect] setProgressImageBlock:[OCMArg isNil] callbackQueue:OCMOCK_ANY withDownloadIdentifier:@0];
|
||||
[node exitInterfaceState:ASInterfaceStateInHierarchy];
|
||||
[downloader verify];
|
||||
}
|
||||
|
||||
- (void)testThatProgressBlockIsSetAndClearedCorrectlyOnChangeURL
|
||||
{
|
||||
[node layer];
|
||||
[node enterInterfaceState:ASInterfaceStateInHierarchy];
|
||||
|
||||
// Set URL while visible, should set progress block
|
||||
[[downloader expect] setProgressImageBlock:[OCMArg isNotNil] callbackQueue:OCMOCK_ANY withDownloadIdentifier:@0];
|
||||
node.URL = [NSURL URLWithString:@"http://imageA"];
|
||||
[downloader verifyWithDelay:5];
|
||||
|
||||
// Change URL while visible, should clear prior block and set new one
|
||||
[[downloader expect] setProgressImageBlock:[OCMArg isNil] callbackQueue:OCMOCK_ANY withDownloadIdentifier:@0];
|
||||
[[downloader expect] cancelImageDownloadForIdentifier:@0];
|
||||
[[downloader expect] setProgressImageBlock:[OCMArg isNotNil] callbackQueue:OCMOCK_ANY withDownloadIdentifier:@1];
|
||||
node.URL = [NSURL URLWithString:@"http://imageB"];
|
||||
[downloader verifyWithDelay:5];
|
||||
}
|
||||
|
||||
- (void)testThatSettingAnImageWillStayForEnteringAndExitingPreloadState
|
||||
{
|
||||
UIImage *image = [[UIImage alloc] init];
|
||||
ASNetworkImageNode *networkImageNode = [[ASNetworkImageNode alloc] init];
|
||||
networkImageNode.image = image;
|
||||
[networkImageNode enterHierarchyState:ASHierarchyStateRangeManaged]; // Ensures didExitPreloadState is called
|
||||
XCTAssertEqualObjects(image, networkImageNode.image);
|
||||
[networkImageNode enterInterfaceState:ASInterfaceStatePreload];
|
||||
XCTAssertEqualObjects(image, networkImageNode.image);
|
||||
[networkImageNode exitInterfaceState:ASInterfaceStatePreload];
|
||||
XCTAssertEqualObjects(image, networkImageNode.image);
|
||||
[networkImageNode exitHierarchyState:ASHierarchyStateRangeManaged];
|
||||
XCTAssertEqualObjects(image, networkImageNode.image);
|
||||
}
|
||||
|
||||
- (void)testThatSettingADefaultImageWillStayForEnteringAndExitingPreloadState
|
||||
{
|
||||
UIImage *image = [[UIImage alloc] init];
|
||||
ASNetworkImageNode *networkImageNode = [[ASNetworkImageNode alloc] init];
|
||||
networkImageNode.defaultImage = image;
|
||||
[networkImageNode enterHierarchyState:ASHierarchyStateRangeManaged]; // Ensures didExitPreloadState is called
|
||||
XCTAssertEqualObjects(image, networkImageNode.defaultImage);
|
||||
[networkImageNode enterInterfaceState:ASInterfaceStatePreload];
|
||||
XCTAssertEqualObjects(image, networkImageNode.defaultImage);
|
||||
[networkImageNode exitInterfaceState:ASInterfaceStatePreload];
|
||||
XCTAssertEqualObjects(image, networkImageNode.defaultImage);
|
||||
[networkImageNode exitHierarchyState:ASHierarchyStateRangeManaged];
|
||||
XCTAssertEqualObjects(image, networkImageNode.defaultImage);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTestImageCache
|
||||
|
||||
- (void)cachedImageWithURL:(NSURL *)URL callbackQueue:(dispatch_queue_t)callbackQueue completion:(ASImageCacherCompletion)completion
|
||||
{
|
||||
completion(nil);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTestImageDownloader {
|
||||
NSInteger _currentDownloadID;
|
||||
}
|
||||
|
||||
- (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier
|
||||
{
|
||||
// nop
|
||||
}
|
||||
|
||||
- (id)downloadImageWithURL:(NSURL *)URL callbackQueue:(dispatch_queue_t)callbackQueue downloadProgress:(ASImageDownloaderProgress)downloadProgress completion:(ASImageDownloaderCompletion)completion
|
||||
{
|
||||
return @(_currentDownloadID++);
|
||||
}
|
||||
|
||||
- (void)setProgressImageBlock:(ASImageDownloaderProgressImage)progressBlock callbackQueue:(dispatch_queue_t)callbackQueue withDownloadIdentifier:(id)downloadIdentifier
|
||||
{
|
||||
// nop
|
||||
}
|
||||
@end
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
//
|
||||
// ASOverlayLayoutSpecSnapshotTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutSpecSnapshotTestsHelper.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASOverlayLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASCenterLayoutSpec.h>
|
||||
|
||||
static const ASSizeRange kSize = {{320, 320}, {320, 320}};
|
||||
|
||||
@interface ASOverlayLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase
|
||||
@end
|
||||
|
||||
@implementation ASOverlayLayoutSpecSnapshotTests
|
||||
|
||||
- (void)testOverlay
|
||||
{
|
||||
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor blueColor]);
|
||||
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor blackColor], {20, 20});
|
||||
|
||||
ASLayoutSpec *layoutSpec =
|
||||
[ASOverlayLayoutSpec
|
||||
overlayLayoutSpecWithChild:backgroundNode
|
||||
overlay:
|
||||
[ASCenterLayoutSpec
|
||||
centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY
|
||||
sizingOptions:{}
|
||||
child:foregroundNode]];
|
||||
|
||||
[self testLayoutSpec:layoutSpec sizeRange:kSize subnodes:@[backgroundNode, foregroundNode] identifier: nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,179 +0,0 @@
|
|||
//
|
||||
// ASPagerNodeTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
@interface ASPagerNodeTestDataSource : NSObject <ASPagerDataSource>
|
||||
@end
|
||||
|
||||
@implementation ASPagerNodeTestDataSource
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (!(self = [super init])) {
|
||||
return nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index
|
||||
{
|
||||
return [[ASCellNode alloc] init];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASPagerNodeTestController: UIViewController
|
||||
@property (nonatomic) ASPagerNodeTestDataSource *testDataSource;
|
||||
@property (nonatomic) ASPagerNode *pagerNode;
|
||||
@end
|
||||
|
||||
@implementation ASPagerNodeTestController
|
||||
|
||||
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
|
||||
{
|
||||
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
|
||||
if (self) {
|
||||
// Populate these immediately so that they're not unexpectedly nil during tests.
|
||||
self.testDataSource = [[ASPagerNodeTestDataSource alloc] init];
|
||||
|
||||
self.pagerNode = [[ASPagerNode alloc] init];
|
||||
self.pagerNode.dataSource = self.testDataSource;
|
||||
|
||||
[self.view addSubnode:self.pagerNode];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASPagerNodeTests : XCTestCase
|
||||
@property (nonatomic) ASPagerNode *pagerNode;
|
||||
|
||||
@property (nonatomic) ASPagerNodeTestDataSource *testDataSource;
|
||||
@end
|
||||
|
||||
@implementation ASPagerNodeTests
|
||||
|
||||
- (void)testPagerReturnsIndexOfPages
|
||||
{
|
||||
ASPagerNodeTestController *testController = [self testController];
|
||||
|
||||
ASCellNode *cellNode = [testController.pagerNode nodeForPageAtIndex:0];
|
||||
|
||||
XCTAssertEqual([testController.pagerNode indexOfPageWithNode:cellNode], 0);
|
||||
}
|
||||
|
||||
- (void)testPagerReturnsNotFoundForCellThatDontExistInPager
|
||||
{
|
||||
ASPagerNodeTestController *testController = [self testController];
|
||||
|
||||
ASCellNode *badNode = [[ASCellNode alloc] init];
|
||||
|
||||
XCTAssertEqual([testController.pagerNode indexOfPageWithNode:badNode], NSNotFound);
|
||||
}
|
||||
|
||||
- (void)testScrollPageToIndex
|
||||
{
|
||||
ASPagerNodeTestController *testController = [self testController];
|
||||
testController.pagerNode.frame = CGRectMake(0, 0, 500, 500);
|
||||
[testController.pagerNode scrollToPageAtIndex:1 animated:false];
|
||||
|
||||
XCTAssertEqual(testController.pagerNode.currentPageIndex, 1);
|
||||
}
|
||||
|
||||
- (ASPagerNodeTestController *)testController
|
||||
{
|
||||
ASPagerNodeTestController *testController = [[ASPagerNodeTestController alloc] initWithNibName:nil bundle:nil];
|
||||
UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
[window makeKeyAndVisible];
|
||||
window.rootViewController = testController;
|
||||
|
||||
[testController.pagerNode reloadData];
|
||||
[testController.pagerNode setNeedsLayout];
|
||||
|
||||
return testController;
|
||||
}
|
||||
|
||||
// Disabled due to flakiness https://github.com/facebook/AsyncDisplayKit/issues/2818
|
||||
- (void)DISABLED_testThatRootPagerNodeDoesGetTheRightInsetWhilePoppingBack
|
||||
{
|
||||
UICollectionViewCell *cell = nil;
|
||||
|
||||
UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||
node.automaticallyManagesSubnodes = YES;
|
||||
|
||||
ASPagerNodeTestDataSource *dataSource = [[ASPagerNodeTestDataSource alloc] init];
|
||||
ASPagerNode *pagerNode = [[ASPagerNode alloc] init];
|
||||
pagerNode.dataSource = dataSource;
|
||||
node.layoutSpecBlock = ^(ASDisplayNode *node, ASSizeRange constrainedSize){
|
||||
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:pagerNode];
|
||||
};
|
||||
ASViewController *vc = [[ASViewController alloc] initWithNode:node];
|
||||
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
|
||||
window.rootViewController = nav;
|
||||
[window makeKeyAndVisible];
|
||||
[window layoutIfNeeded];
|
||||
|
||||
// Wait until view controller is visible
|
||||
XCTestExpectation *e = [self expectationWithDescription:@"Transition completed"];
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
[e fulfill];
|
||||
});
|
||||
[self waitForExpectationsWithTimeout:2 handler:nil];
|
||||
|
||||
// Test initial values
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
cell = [pagerNode.view cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
#pragma clang diagnostic pop
|
||||
XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(node.frame));
|
||||
XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(cell.frame));
|
||||
XCTAssertEqual(pagerNode.contentOffset.y, 0);
|
||||
XCTAssertEqual(pagerNode.contentInset.top, 0);
|
||||
|
||||
e = [self expectationWithDescription:@"Transition completed"];
|
||||
// Push another view controller
|
||||
UIViewController *vc2 = [[UIViewController alloc] init];
|
||||
vc2.view.frame = nav.view.bounds;
|
||||
vc2.view.backgroundColor = [UIColor blueColor];
|
||||
[nav pushViewController:vc2 animated:YES];
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.505 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
[e fulfill];
|
||||
});
|
||||
[self waitForExpectationsWithTimeout:2 handler:nil];
|
||||
|
||||
// Pop view controller
|
||||
e = [self expectationWithDescription:@"Transition completed"];
|
||||
[vc2.navigationController popViewControllerAnimated:YES];
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.505 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
[e fulfill];
|
||||
});
|
||||
[self waitForExpectationsWithTimeout:2 handler:nil];
|
||||
|
||||
// Test values again after popping the view controller
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
cell = [pagerNode.view cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
#pragma clang diagnostic pop
|
||||
XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(node.frame));
|
||||
XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(cell.frame));
|
||||
XCTAssertEqual(pagerNode.contentOffset.y, 0);
|
||||
XCTAssertEqual(pagerNode.contentInset.top, 0);
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
//
|
||||
// ASPerformanceTestContext.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <XCTest/XCTestAssertionsImpl.h>
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
|
||||
#define ASXCTAssertRelativePerformanceInRange(test, caseName, min, max) \
|
||||
_XCTPrimitiveAssertLessThanOrEqual(self, test.results[caseName].relativePerformance, @#caseName, max, @#max);\
|
||||
_XCTPrimitiveAssertGreaterThanOrEqual(self, test.results[caseName].relativePerformance, @#caseName, min, @#min)
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef void (^ASTestPerformanceCaseBlock)(NSUInteger i, dispatch_block_t startMeasuring, dispatch_block_t stopMeasuring);
|
||||
|
||||
@interface ASPerformanceTestResult : NSObject
|
||||
@property (nonatomic, readonly) NSTimeInterval timePer1000;
|
||||
@property (nonatomic, readonly) NSString *caseName;
|
||||
|
||||
@property (nonatomic, readonly, getter=isReferenceCase) BOOL referenceCase;
|
||||
@property (nonatomic, readonly) float relativePerformance;
|
||||
|
||||
@property (nonatomic, readonly) NSMutableDictionary *userInfo;
|
||||
@end
|
||||
|
||||
@interface ASPerformanceTestContext : NSObject
|
||||
|
||||
/**
|
||||
* The first case you add here will be considered the reference case.
|
||||
*/
|
||||
- (void)addCaseWithName:(NSString *)caseName block:(AS_NOESCAPE ASTestPerformanceCaseBlock)block;
|
||||
|
||||
@property (nonatomic, copy, readonly) NSDictionary<NSString *, ASPerformanceTestResult *> *results;
|
||||
|
||||
- (BOOL)areAllUserInfosEqual;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
//
|
||||
// ASPerformanceTestContext.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASPerformanceTestContext.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <QuartzCore/CABase.h>
|
||||
|
||||
@interface ASPerformanceTestResult ()
|
||||
@property (nonatomic) NSTimeInterval timePer1000;
|
||||
@property (nonatomic) NSString *caseName;
|
||||
|
||||
@property (nonatomic, getter=isReferenceCase) BOOL referenceCase;
|
||||
@property (nonatomic) float relativePerformance;
|
||||
@end
|
||||
|
||||
@implementation ASPerformanceTestResult
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_userInfo = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
NSString *userInfoStr = [_userInfo.description stringByReplacingOccurrencesOfString:@"\n" withString:@" "];
|
||||
return [NSString stringWithFormat:@"<%-20s: time-per-1000=%04.2f rel-perf=%04.2f user-info=%@>", _caseName.UTF8String, _timePer1000, _relativePerformance, userInfoStr];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASPerformanceTestContext {
|
||||
NSMutableDictionary *_results;
|
||||
NSInteger _iterationCount;
|
||||
ASPerformanceTestResult * _Nullable _referenceResult;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_iterationCount = 1E4;
|
||||
_results = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSDictionary<NSString *, ASPerformanceTestResult *> *)results
|
||||
{
|
||||
return _results;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
/**
|
||||
* I know this seems wacky but it's a pain to have to put this in every single test method.
|
||||
*/
|
||||
NSLog(@"%@", self.description);
|
||||
}
|
||||
|
||||
- (BOOL)areAllUserInfosEqual
|
||||
{
|
||||
ASDisplayNodeAssert(_results.count >= 2, nil);
|
||||
NSEnumerator *resultsEnumerator = [_results objectEnumerator];
|
||||
NSDictionary *userInfo = [[resultsEnumerator nextObject] userInfo];
|
||||
for (ASPerformanceTestResult *otherResult in resultsEnumerator) {
|
||||
if ([userInfo isEqualToDictionary:otherResult.userInfo] == NO) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)addCaseWithName:(NSString *)caseName block:(AS_NOESCAPE ASTestPerformanceCaseBlock)block
|
||||
{
|
||||
ASDisplayNodeAssert(_results[caseName] == nil, @"Already have a case named %@", caseName);
|
||||
ASPerformanceTestResult *result = [[ASPerformanceTestResult alloc] init];
|
||||
result.caseName = caseName;
|
||||
result.timePer1000 = [self _testPerformanceForCaseWithBlock:block] / (_iterationCount / 1000);
|
||||
if (_referenceResult == nil) {
|
||||
result.referenceCase = YES;
|
||||
result.relativePerformance = 1.0f;
|
||||
_referenceResult = result;
|
||||
} else {
|
||||
result.relativePerformance = _referenceResult.timePer1000 / result.timePer1000;
|
||||
}
|
||||
_results[caseName] = result;
|
||||
}
|
||||
|
||||
/// Returns total work time
|
||||
- (CFTimeInterval)_testPerformanceForCaseWithBlock:(AS_NOESCAPE ASTestPerformanceCaseBlock)block
|
||||
{
|
||||
__block CFTimeInterval time = 0;
|
||||
for (NSInteger i = 0; i < _iterationCount; i++) {
|
||||
__block CFTimeInterval start = 0;
|
||||
__block BOOL calledStop = NO;
|
||||
@autoreleasepool {
|
||||
block(i, ^{
|
||||
ASDisplayNodeAssert(start == 0, @"Called startMeasuring block twice.");
|
||||
start = CACurrentMediaTime();
|
||||
}, ^{
|
||||
time += (CACurrentMediaTime() - start);
|
||||
ASDisplayNodeAssert(calledStop == NO, @"Called stopMeasuring block twice.");
|
||||
ASDisplayNodeAssert(start != 0, @"Failed to call startMeasuring block");
|
||||
calledStop = YES;
|
||||
});
|
||||
}
|
||||
|
||||
ASDisplayNodeAssert(calledStop, @"Failed to call stopMeasuring block.");
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
NSMutableString *str = [NSMutableString stringWithString:@"Results:\n"];
|
||||
for (ASPerformanceTestResult *result in [_results objectEnumerator]) {
|
||||
[str appendFormat:@"\t%@\n", result];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
//
|
||||
// ASPhotosFrameworkImageRequestTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#if AS_USE_PHOTOS
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/ASPhotosFrameworkImageRequest.h>
|
||||
|
||||
static NSString *const kTestAssetID = @"testAssetID";
|
||||
|
||||
@interface ASPhotosFrameworkImageRequestTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASPhotosFrameworkImageRequestTests
|
||||
|
||||
#pragma mark Example Data
|
||||
|
||||
+ (ASPhotosFrameworkImageRequest *)exampleImageRequest
|
||||
{
|
||||
ASPhotosFrameworkImageRequest *req = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:kTestAssetID];
|
||||
req.options.networkAccessAllowed = YES;
|
||||
req.options.normalizedCropRect = CGRectMake(0.2, 0.1, 0.6, 0.8);
|
||||
req.targetSize = CGSizeMake(1024, 1536);
|
||||
req.contentMode = PHImageContentModeAspectFill;
|
||||
req.options.version = PHImageRequestOptionsVersionOriginal;
|
||||
req.options.resizeMode = PHImageRequestOptionsResizeModeFast;
|
||||
return req;
|
||||
}
|
||||
|
||||
+ (NSURL *)urlForExampleImageRequest
|
||||
{
|
||||
NSString *str = [NSString stringWithFormat:@"ph://%@?width=1024&height=1536&version=2&contentmode=1&network=1&resizemode=1&deliverymode=0&crop_x=0.2&crop_y=0.1&crop_w=0.6&crop_h=0.8", kTestAssetID];
|
||||
return [NSURL URLWithString:str];
|
||||
}
|
||||
|
||||
#pragma mark Test cases
|
||||
|
||||
- (void)testThatConvertingToURLWorks
|
||||
{
|
||||
XCTAssertEqualObjects([self.class exampleImageRequest].url, [self.class urlForExampleImageRequest]);
|
||||
}
|
||||
|
||||
- (void)testThatParsingFromURLWorks
|
||||
{
|
||||
NSURL *url = [self.class urlForExampleImageRequest];
|
||||
XCTAssertEqualObjects([ASPhotosFrameworkImageRequest requestWithURL:url], [self.class exampleImageRequest]);
|
||||
}
|
||||
|
||||
- (void)testThatCopyingWorks
|
||||
{
|
||||
ASPhotosFrameworkImageRequest *example = [self.class exampleImageRequest];
|
||||
ASPhotosFrameworkImageRequest *copy = [[self.class exampleImageRequest] copy];
|
||||
XCTAssertEqualObjects(example, copy);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
//
|
||||
// ASRatioLayoutSpecSnapshotTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutSpecSnapshotTestsHelper.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASRatioLayoutSpec.h>
|
||||
|
||||
static const ASSizeRange kFixedSize = {{0, 0}, {100, 100}};
|
||||
|
||||
@interface ASRatioLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase
|
||||
@end
|
||||
|
||||
@implementation ASRatioLayoutSpecSnapshotTests
|
||||
|
||||
- (void)testRatioLayoutSpecWithRatio:(CGFloat)ratio childSize:(CGSize)childSize identifier:(NSString *)identifier
|
||||
{
|
||||
ASDisplayNode *subnode = ASDisplayNodeWithBackgroundColor([UIColor greenColor], childSize);
|
||||
|
||||
ASLayoutSpec *layoutSpec = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:ratio child:subnode];
|
||||
|
||||
[self testLayoutSpec:layoutSpec sizeRange:kFixedSize subnodes:@[subnode] identifier:identifier];
|
||||
}
|
||||
|
||||
- (void)testRatioLayout
|
||||
{
|
||||
[self testRatioLayoutSpecWithRatio:0.5 childSize:CGSizeMake(100, 100) identifier:@"HalfRatio"];
|
||||
[self testRatioLayoutSpecWithRatio:2.0 childSize:CGSizeMake(100, 100) identifier:@"DoubleRatio"];
|
||||
[self testRatioLayoutSpecWithRatio:7.0 childSize:CGSizeMake(100, 100) identifier:@"SevenTimesRatio"];
|
||||
[self testRatioLayoutSpecWithRatio:10.0 childSize:CGSizeMake(20, 200) identifier:@"TenTimesRatioWithItemTooBig"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,184 +0,0 @@
|
|||
//
|
||||
// ASRecursiveUnfairLockTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASTestCase.h"
|
||||
#import <AsyncDisplayKit/ASRecursiveUnfairLock.h>
|
||||
#import <stdatomic.h>
|
||||
#import <os/lock.h>
|
||||
|
||||
@interface ASRecursiveUnfairLockTests : ASTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASRecursiveUnfairLockTests {
|
||||
ASRecursiveUnfairLock lock;
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
lock = AS_RECURSIVE_UNFAIR_LOCK_INIT;
|
||||
}
|
||||
|
||||
- (void)testTheAtomicIsLockFree
|
||||
{
|
||||
XCTAssertTrue(atomic_is_lock_free(&lock._thread));
|
||||
}
|
||||
|
||||
- (void)testRelockingFromSameThread
|
||||
{
|
||||
ASRecursiveUnfairLockLock(&lock);
|
||||
ASRecursiveUnfairLockLock(&lock);
|
||||
ASRecursiveUnfairLockUnlock(&lock);
|
||||
// Now try locking from another thread.
|
||||
XCTestExpectation *e1 = [self expectationWithDescription:@"Other thread tried lock."];
|
||||
[NSThread detachNewThreadWithBlock:^{
|
||||
XCTAssertFalse(ASRecursiveUnfairLockTryLock(&self->lock));
|
||||
[e1 fulfill];
|
||||
}];
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
ASRecursiveUnfairLockUnlock(&lock);
|
||||
|
||||
XCTestExpectation *e2 = [self expectationWithDescription:@"Other thread tried lock again"];
|
||||
[NSThread detachNewThreadWithBlock:^{
|
||||
XCTAssertTrue(ASRecursiveUnfairLockTryLock(&self->lock));
|
||||
ASRecursiveUnfairLockUnlock(&self->lock);
|
||||
[e2 fulfill];
|
||||
}];
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
}
|
||||
|
||||
- (void)testThatUnlockingWithoutHoldingMakesAssertion
|
||||
{
|
||||
#ifdef NS_BLOCK_ASSERTIONS
|
||||
#warning Assertions should be on for `testThatUnlockingWithoutHoldingMakesAssertion`
|
||||
NSLog(@"Passing because assertions are off.");
|
||||
#else
|
||||
ASRecursiveUnfairLockLock(&lock);
|
||||
XCTestExpectation *e1 = [self expectationWithDescription:@"Other thread tried lock."];
|
||||
[NSThread detachNewThreadWithBlock:^{
|
||||
XCTAssertThrows(ASRecursiveUnfairLockUnlock(&lock));
|
||||
[e1 fulfill];
|
||||
}];
|
||||
[self waitForExpectationsWithTimeout:10 handler:nil];
|
||||
ASRecursiveUnfairLockUnlock(&lock);
|
||||
#endif
|
||||
}
|
||||
|
||||
#define CHAOS_TEST_BODY(contested, prefix, infix, postfix) \
|
||||
dispatch_group_t g = dispatch_group_create(); \
|
||||
for (int i = 0; i < (contested ? 16 : 2); i++) {\
|
||||
dispatch_group_enter(g);\
|
||||
[NSThread detachNewThreadWithBlock:^{\
|
||||
for (int i = 0; i < 20000; i++) {\
|
||||
prefix;\
|
||||
value += 150;\
|
||||
infix;\
|
||||
value -= 150;\
|
||||
postfix;\
|
||||
}\
|
||||
dispatch_group_leave(g);\
|
||||
}];\
|
||||
}\
|
||||
dispatch_group_wait(g, DISPATCH_TIME_FOREVER);
|
||||
|
||||
#pragma mark - Correctness Tests
|
||||
|
||||
- (void)testRecursiveUnfairLockContested
|
||||
{
|
||||
__block int value = 0;
|
||||
[self measureBlock:^{
|
||||
CHAOS_TEST_BODY(YES, ASRecursiveUnfairLockLock(&lock), {}, ASRecursiveUnfairLockUnlock(&lock));
|
||||
}];
|
||||
XCTAssertEqual(value, 0);
|
||||
}
|
||||
|
||||
- (void)testRecursiveUnfairLockUncontested
|
||||
{
|
||||
__block int value = 0;
|
||||
[self measureBlock:^{
|
||||
CHAOS_TEST_BODY(NO, ASRecursiveUnfairLockLock(&lock), {}, ASRecursiveUnfairLockUnlock(&lock));
|
||||
}];
|
||||
XCTAssertEqual(value, 0);
|
||||
}
|
||||
|
||||
#pragma mark - Lock performance tests
|
||||
|
||||
#if RUN_LOCK_PERF_TESTS
|
||||
- (void)testNoLockContested
|
||||
{
|
||||
__block int value = 0;
|
||||
[self measureBlock:^{
|
||||
CHAOS_TEST_BODY(YES, {}, {}, {});
|
||||
}];
|
||||
XCTAssertNotEqual(value, 0);
|
||||
}
|
||||
|
||||
- (void)testPlainUnfairLockContested
|
||||
{
|
||||
__block int value = 0;
|
||||
__block os_unfair_lock unfairLock = OS_UNFAIR_LOCK_INIT;
|
||||
[self measureBlock:^{
|
||||
CHAOS_TEST_BODY(YES, os_unfair_lock_lock(&unfairLock), {}, os_unfair_lock_unlock(&unfairLock));
|
||||
}];
|
||||
XCTAssertEqual(value, 0);
|
||||
}
|
||||
|
||||
- (void)testRecursiveMutexContested
|
||||
{
|
||||
__block int value = 0;
|
||||
pthread_mutexattr_t attr;
|
||||
pthread_mutexattr_init (&attr);
|
||||
pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
__block pthread_mutex_t m;
|
||||
pthread_mutex_init (&m, &attr);
|
||||
pthread_mutexattr_destroy (&attr);
|
||||
|
||||
[self measureBlock:^{
|
||||
CHAOS_TEST_BODY(YES, pthread_mutex_lock(&m), {}, pthread_mutex_unlock(&m));
|
||||
}];
|
||||
pthread_mutex_destroy(&m);
|
||||
}
|
||||
|
||||
- (void)testNoLockUncontested
|
||||
{
|
||||
__block int value = 0;
|
||||
[self measureBlock:^{
|
||||
CHAOS_TEST_BODY(NO, {}, {}, {});
|
||||
}];
|
||||
XCTAssertNotEqual(value, 0);
|
||||
}
|
||||
|
||||
- (void)testPlainUnfairLockUncontested
|
||||
{
|
||||
__block int value = 0;
|
||||
__block os_unfair_lock unfairLock = OS_UNFAIR_LOCK_INIT;
|
||||
[self measureBlock:^{
|
||||
CHAOS_TEST_BODY(NO, os_unfair_lock_lock(&unfairLock), {}, os_unfair_lock_unlock(&unfairLock));
|
||||
}];
|
||||
XCTAssertEqual(value, 0);
|
||||
}
|
||||
|
||||
- (void)testRecursiveMutexUncontested
|
||||
{
|
||||
__block int value = 0;
|
||||
pthread_mutexattr_t attr;
|
||||
pthread_mutexattr_init (&attr);
|
||||
pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
__block pthread_mutex_t m;
|
||||
pthread_mutex_init (&m, &attr);
|
||||
pthread_mutexattr_destroy (&attr);
|
||||
|
||||
[self measureBlock:^{
|
||||
CHAOS_TEST_BODY(NO, pthread_mutex_lock(&m), {}, pthread_mutex_unlock(&m));
|
||||
}];
|
||||
pthread_mutex_destroy(&m);
|
||||
}
|
||||
|
||||
#endif // RUN_LOCK_PERF_TESTS
|
||||
@end
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
//
|
||||
// ASRelativeLayoutSpecSnapshotTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutSpecSnapshotTestsHelper.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASBackgroundLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASRelativeLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASStackLayoutSpec.h>
|
||||
|
||||
static const ASSizeRange kSize = {{100, 120}, {320, 160}};
|
||||
|
||||
@interface ASRelativeLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase
|
||||
@end
|
||||
|
||||
@implementation ASRelativeLayoutSpecSnapshotTests
|
||||
|
||||
#pragma mark - XCTestCase
|
||||
|
||||
- (void)testWithOptions
|
||||
{
|
||||
[self testAllVerticalPositionsForHorizontalPosition:ASRelativeLayoutSpecPositionStart];
|
||||
[self testAllVerticalPositionsForHorizontalPosition:ASRelativeLayoutSpecPositionCenter];
|
||||
[self testAllVerticalPositionsForHorizontalPosition:ASRelativeLayoutSpecPositionEnd];
|
||||
|
||||
}
|
||||
|
||||
- (void)testAllVerticalPositionsForHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition
|
||||
{
|
||||
[self testWithHorizontalPosition:horizontalPosition verticalPosition:ASRelativeLayoutSpecPositionStart sizingOptions:{}];
|
||||
[self testWithHorizontalPosition:horizontalPosition verticalPosition:ASRelativeLayoutSpecPositionCenter sizingOptions:{}];
|
||||
[self testWithHorizontalPosition:horizontalPosition verticalPosition:ASRelativeLayoutSpecPositionEnd sizingOptions:{}];
|
||||
}
|
||||
|
||||
- (void)testWithSizingOptions
|
||||
{
|
||||
[self testWithHorizontalPosition:ASRelativeLayoutSpecPositionStart
|
||||
verticalPosition:ASRelativeLayoutSpecPositionStart
|
||||
sizingOptions:ASRelativeLayoutSpecSizingOptionDefault];
|
||||
[self testWithHorizontalPosition:ASRelativeLayoutSpecPositionStart
|
||||
verticalPosition:ASRelativeLayoutSpecPositionStart
|
||||
sizingOptions:ASRelativeLayoutSpecSizingOptionMinimumWidth];
|
||||
[self testWithHorizontalPosition:ASRelativeLayoutSpecPositionStart
|
||||
verticalPosition:ASRelativeLayoutSpecPositionStart
|
||||
sizingOptions:ASRelativeLayoutSpecSizingOptionMinimumHeight];
|
||||
[self testWithHorizontalPosition:ASRelativeLayoutSpecPositionStart
|
||||
verticalPosition:ASRelativeLayoutSpecPositionStart
|
||||
sizingOptions:ASRelativeLayoutSpecSizingOptionMinimumSize];
|
||||
}
|
||||
|
||||
- (void)testWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition
|
||||
verticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition
|
||||
sizingOptions:(ASRelativeLayoutSpecSizingOption)sizingOptions
|
||||
{
|
||||
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
|
||||
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor greenColor], CGSizeMake(70, 100));
|
||||
|
||||
ASLayoutSpec *layoutSpec =
|
||||
[ASBackgroundLayoutSpec
|
||||
backgroundLayoutSpecWithChild:
|
||||
[ASRelativeLayoutSpec
|
||||
relativePositionLayoutSpecWithHorizontalPosition:horizontalPosition
|
||||
verticalPosition:verticalPosition
|
||||
sizingOption:sizingOptions
|
||||
child:foregroundNode]
|
||||
background:backgroundNode];
|
||||
|
||||
[self testLayoutSpec:layoutSpec
|
||||
sizeRange:kSize
|
||||
subnodes:@[backgroundNode, foregroundNode]
|
||||
identifier:suffixForPositionOptions(horizontalPosition, verticalPosition, sizingOptions)];
|
||||
}
|
||||
|
||||
static NSString *suffixForPositionOptions(ASRelativeLayoutSpecPosition horizontalPosition,
|
||||
ASRelativeLayoutSpecPosition verticalPosition,
|
||||
ASRelativeLayoutSpecSizingOption sizingOptions)
|
||||
{
|
||||
NSMutableString *suffix = [NSMutableString string];
|
||||
|
||||
if (horizontalPosition == ASRelativeLayoutSpecPositionCenter) {
|
||||
[suffix appendString:@"CenterX"];
|
||||
} else if (horizontalPosition == ASRelativeLayoutSpecPositionEnd) {
|
||||
[suffix appendString:@"EndX"];
|
||||
}
|
||||
|
||||
if (verticalPosition == ASRelativeLayoutSpecPositionCenter) {
|
||||
[suffix appendString:@"CenterY"];
|
||||
} else if (verticalPosition == ASRelativeLayoutSpecPositionEnd) {
|
||||
[suffix appendString:@"EndY"];
|
||||
}
|
||||
|
||||
if ((sizingOptions & ASRelativeLayoutSpecSizingOptionMinimumWidth) != 0) {
|
||||
[suffix appendString:@"SizingMinimumWidth"];
|
||||
}
|
||||
|
||||
if ((sizingOptions & ASRelativeLayoutSpecSizingOptionMinimumHeight) != 0) {
|
||||
[suffix appendString:@"SizingMinimumHeight"];
|
||||
}
|
||||
|
||||
return suffix;
|
||||
}
|
||||
|
||||
- (void)testMinimumSizeRangeIsGivenToChildWhenNotPositioning
|
||||
{
|
||||
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
|
||||
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor], CGSizeMake(10, 10));
|
||||
foregroundNode.style.flexGrow = 1;
|
||||
|
||||
ASLayoutSpec *childSpec =
|
||||
[ASBackgroundLayoutSpec
|
||||
backgroundLayoutSpecWithChild:
|
||||
[ASStackLayoutSpec
|
||||
stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical
|
||||
spacing:0
|
||||
justifyContent:ASStackLayoutJustifyContentStart
|
||||
alignItems:ASStackLayoutAlignItemsStart
|
||||
children:@[foregroundNode]]
|
||||
background:backgroundNode];
|
||||
|
||||
ASRelativeLayoutSpec *layoutSpec =
|
||||
[ASRelativeLayoutSpec
|
||||
relativePositionLayoutSpecWithHorizontalPosition:ASRelativeLayoutSpecPositionNone
|
||||
verticalPosition:ASRelativeLayoutSpecPositionNone
|
||||
sizingOption:{}
|
||||
child:childSpec];
|
||||
|
||||
[self testLayoutSpec:layoutSpec sizeRange:kSize subnodes:@[backgroundNode, foregroundNode] identifier:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,201 +0,0 @@
|
|||
//
|
||||
// ASRunLoopQueueTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASTestCase.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASRunLoopQueue.h>
|
||||
|
||||
#import "ASDisplayNodeTestsHelper.h"
|
||||
|
||||
static NSTimeInterval const kRunLoopRunTime = 0.001; // Allow the RunLoop to run for one millisecond each time.
|
||||
|
||||
@interface QueueObject : NSObject <ASCATransactionQueueObserving>
|
||||
@property (nonatomic) BOOL queueObjectProcessed;
|
||||
@end
|
||||
|
||||
@implementation QueueObject
|
||||
- (void)prepareForCATransactionCommit
|
||||
{
|
||||
self.queueObjectProcessed = YES;
|
||||
}
|
||||
@end
|
||||
|
||||
@interface ASRunLoopQueueTests : ASTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASRunLoopQueueTests
|
||||
|
||||
#pragma mark enqueue tests
|
||||
|
||||
- (void)testEnqueueNilObjectsToQueue
|
||||
{
|
||||
ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:nil];
|
||||
id object = nil;
|
||||
[queue enqueue:object];
|
||||
XCTAssertTrue(queue.isEmpty);
|
||||
}
|
||||
|
||||
- (void)testEnqueueSameObjectTwiceToDefaultQueue
|
||||
{
|
||||
id object = [[NSObject alloc] init];
|
||||
__unsafe_unretained id weakObject = object;
|
||||
__block NSUInteger dequeuedCount = 0;
|
||||
ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:^(id _Nonnull dequeuedItem, BOOL isQueueDrained) {
|
||||
if (dequeuedItem == weakObject) {
|
||||
dequeuedCount++;
|
||||
}
|
||||
}];
|
||||
[queue enqueue:object];
|
||||
[queue enqueue:object];
|
||||
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]];
|
||||
XCTAssert(dequeuedCount == 1);
|
||||
}
|
||||
|
||||
- (void)testEnqueueSameObjectTwiceToNonExclusiveMembershipQueue
|
||||
{
|
||||
id object = [[NSObject alloc] init];
|
||||
__unsafe_unretained id weakObject = object;
|
||||
__block NSUInteger dequeuedCount = 0;
|
||||
ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:^(id _Nonnull dequeuedItem, BOOL isQueueDrained) {
|
||||
if (dequeuedItem == weakObject) {
|
||||
dequeuedCount++;
|
||||
}
|
||||
}];
|
||||
queue.ensureExclusiveMembership = NO;
|
||||
[queue enqueue:object];
|
||||
[queue enqueue:object];
|
||||
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]];
|
||||
XCTAssert(dequeuedCount == 2);
|
||||
}
|
||||
|
||||
#pragma mark processQueue tests
|
||||
|
||||
- (void)testDefaultQueueProcessObjectsOneAtATime
|
||||
{
|
||||
ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:^(id _Nonnull dequeuedItem, BOOL isQueueDrained) {
|
||||
[NSThread sleepForTimeInterval:kRunLoopRunTime * 2]; // So each element takes more time than the available
|
||||
}];
|
||||
[queue enqueue:[[NSObject alloc] init]];
|
||||
[queue enqueue:[[NSObject alloc] init]];
|
||||
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]];
|
||||
XCTAssertFalse(queue.isEmpty);
|
||||
}
|
||||
|
||||
- (void)testQueueProcessObjectsInBatchesOfSpecifiedSize
|
||||
{
|
||||
ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:^(id _Nonnull dequeuedItem, BOOL isQueueDrained) {
|
||||
[NSThread sleepForTimeInterval:kRunLoopRunTime * 2]; // So each element takes more time than the available
|
||||
}];
|
||||
queue.batchSize = 2;
|
||||
[queue enqueue:[[NSObject alloc] init]];
|
||||
[queue enqueue:[[NSObject alloc] init]];
|
||||
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]];
|
||||
XCTAssertTrue(queue.isEmpty);
|
||||
}
|
||||
|
||||
- (void)testQueueOnlySendsIsDrainedForLastObjectInBatch
|
||||
{
|
||||
id objectA = [[NSObject alloc] init];
|
||||
id objectB = [[NSObject alloc] init];
|
||||
__unsafe_unretained id weakObjectA = objectA;
|
||||
__unsafe_unretained id weakObjectB = objectB;
|
||||
__block BOOL isQueueDrainedWhenProcessingA = NO;
|
||||
__block BOOL isQueueDrainedWhenProcessingB = NO;
|
||||
ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:^(id _Nonnull dequeuedItem, BOOL isQueueDrained) {
|
||||
if (dequeuedItem == weakObjectA) {
|
||||
isQueueDrainedWhenProcessingA = isQueueDrained;
|
||||
} else if (dequeuedItem == weakObjectB) {
|
||||
isQueueDrainedWhenProcessingB = isQueueDrained;
|
||||
}
|
||||
}];
|
||||
queue.batchSize = 2;
|
||||
[queue enqueue:objectA];
|
||||
[queue enqueue:objectB];
|
||||
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]];
|
||||
XCTAssertFalse(isQueueDrainedWhenProcessingA);
|
||||
XCTAssertTrue(isQueueDrainedWhenProcessingB);
|
||||
}
|
||||
|
||||
#pragma mark strong/weak tests
|
||||
|
||||
- (void)testStrongQueueRetainsObjects
|
||||
{
|
||||
id object = [[NSObject alloc] init];
|
||||
__unsafe_unretained id weakObject = object;
|
||||
__block BOOL didProcessObject = NO;
|
||||
ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:^(id _Nonnull dequeuedItem, BOOL isQueueDrained) {
|
||||
if (dequeuedItem == weakObject) {
|
||||
didProcessObject = YES;
|
||||
}
|
||||
}];
|
||||
[queue enqueue:object];
|
||||
object = nil;
|
||||
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]];
|
||||
XCTAssertTrue(didProcessObject);
|
||||
}
|
||||
|
||||
- (void)testWeakQueueDoesNotRetainsObjects
|
||||
{
|
||||
id object = [[NSObject alloc] init];
|
||||
__unsafe_unretained id weakObject = object;
|
||||
__block BOOL didProcessObject = NO;
|
||||
ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:NO handler:^(id _Nonnull dequeuedItem, BOOL isQueueDrained) {
|
||||
if (dequeuedItem == weakObject) {
|
||||
didProcessObject = YES;
|
||||
}
|
||||
}];
|
||||
[queue enqueue:object];
|
||||
object = nil;
|
||||
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]];
|
||||
XCTAssertFalse(didProcessObject);
|
||||
}
|
||||
|
||||
- (void)testWeakQueueWithAllDeallocatedObjectsIsDrained
|
||||
{
|
||||
ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:NO handler:nil];
|
||||
id object = [[NSObject alloc] init];
|
||||
[queue enqueue:object];
|
||||
object = nil;
|
||||
XCTAssertFalse(queue.isEmpty);
|
||||
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]];
|
||||
XCTAssertTrue(queue.isEmpty);
|
||||
}
|
||||
|
||||
- (void)testASCATransactionQueueDisable
|
||||
{
|
||||
// Disable coalescing.
|
||||
ASConfiguration *config = [[ASConfiguration alloc] init];
|
||||
config.experimentalFeatures = kNilOptions;
|
||||
[ASConfigurationManager test_resetWithConfiguration:config];
|
||||
|
||||
ASCATransactionQueue *queue = [[ASCATransactionQueue alloc] init];
|
||||
QueueObject *object = [[QueueObject alloc] init];
|
||||
XCTAssertFalse(object.queueObjectProcessed);
|
||||
[queue enqueue:object];
|
||||
XCTAssertTrue(object.queueObjectProcessed);
|
||||
XCTAssertTrue([queue isEmpty]);
|
||||
XCTAssertFalse(queue.enabled);
|
||||
}
|
||||
|
||||
- (void)testASCATransactionQueueProcess
|
||||
{
|
||||
ASConfiguration *config = [[ASConfiguration alloc] initWithDictionary:nil];
|
||||
config.experimentalFeatures = ASExperimentalInterfaceStateCoalescing;
|
||||
[ASConfigurationManager test_resetWithConfiguration:config];
|
||||
|
||||
ASCATransactionQueue *queue = [[ASCATransactionQueue alloc] init];
|
||||
QueueObject *object = [[QueueObject alloc] init];
|
||||
[queue enqueue:object];
|
||||
XCTAssertFalse(object.queueObjectProcessed);
|
||||
ASCATransactionQueueWait(queue);
|
||||
XCTAssertTrue(object.queueObjectProcessed);
|
||||
XCTAssertTrue(queue.enabled);
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,168 +0,0 @@
|
|||
//
|
||||
// ASScrollNodeTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
#import "ASXCTExtensions.h"
|
||||
|
||||
@interface ASScrollNodeTests : XCTestCase
|
||||
|
||||
@property (nonatomic) ASScrollNode *scrollNode;
|
||||
@property (nonatomic) ASDisplayNode *subnode;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASScrollNodeTests
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
ASDisplayNode *subnode = [[ASDisplayNode alloc] init];
|
||||
self.subnode = subnode;
|
||||
|
||||
self.scrollNode = [[ASScrollNode alloc] init];
|
||||
self.scrollNode.scrollableDirections = ASScrollDirectionVerticalDirections;
|
||||
self.scrollNode.automaticallyManagesContentSize = YES;
|
||||
self.scrollNode.automaticallyManagesSubnodes = YES;
|
||||
self.scrollNode.layoutSpecBlock = ^ASLayoutSpec * _Nonnull(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
return [[ASWrapperLayoutSpec alloc] initWithLayoutElement:subnode];
|
||||
};
|
||||
[self.scrollNode view];
|
||||
}
|
||||
|
||||
- (void)testSubnodeLayoutCalculatedWithUnconstrainedMaxSizeInScrollableDirection
|
||||
{
|
||||
CGSize parentSize = CGSizeMake(100, 100);
|
||||
ASSizeRange sizeRange = ASSizeRangeMake(parentSize);
|
||||
|
||||
[self.scrollNode layoutThatFits:sizeRange parentSize:parentSize];
|
||||
|
||||
ASSizeRange subnodeSizeRange = sizeRange;
|
||||
subnodeSizeRange.max.height = CGFLOAT_MAX;
|
||||
XCTAssertEqual(self.scrollNode.scrollableDirections, ASScrollDirectionVerticalDirections);
|
||||
ASXCTAssertEqualSizeRanges(self.subnode.constrainedSizeForCalculatedLayout, subnodeSizeRange);
|
||||
|
||||
// Same test for horizontal scrollable directions
|
||||
self.scrollNode.scrollableDirections = ASScrollDirectionHorizontalDirections;
|
||||
[self.scrollNode layoutThatFits:sizeRange parentSize:parentSize];
|
||||
|
||||
subnodeSizeRange = sizeRange;
|
||||
subnodeSizeRange.max.width = CGFLOAT_MAX;
|
||||
|
||||
ASXCTAssertEqualSizeRanges(self.subnode.constrainedSizeForCalculatedLayout, subnodeSizeRange);
|
||||
}
|
||||
|
||||
- (void)testAutomaticallyManagesContentSizeUnderflow
|
||||
{
|
||||
CGSize subnodeSize = CGSizeMake(100, 100);
|
||||
CGSize parentSize = CGSizeMake(100, 200);
|
||||
ASSizeRange sizeRange = ASSizeRangeUnconstrained;
|
||||
|
||||
self.subnode.style.preferredSize = subnodeSize;
|
||||
|
||||
[self.scrollNode layoutThatFits:sizeRange parentSize:parentSize];
|
||||
[self.scrollNode layout];
|
||||
|
||||
ASXCTAssertEqualSizes(self.scrollNode.calculatedSize, parentSize);
|
||||
ASXCTAssertEqualSizes(self.scrollNode.view.contentSize, subnodeSize);
|
||||
}
|
||||
|
||||
- (void)testAutomaticallyManagesContentSizeOverflow
|
||||
{
|
||||
CGSize subnodeSize = CGSizeMake(100, 500);
|
||||
CGSize parentSize = CGSizeMake(100, 200);
|
||||
ASSizeRange sizeRange = ASSizeRangeUnconstrained;
|
||||
|
||||
self.subnode.style.preferredSize = subnodeSize;
|
||||
|
||||
[self.scrollNode layoutThatFits:sizeRange parentSize:parentSize];
|
||||
[self.scrollNode layout];
|
||||
|
||||
ASXCTAssertEqualSizes(self.scrollNode.calculatedSize, parentSize);
|
||||
ASXCTAssertEqualSizes(self.scrollNode.view.contentSize, subnodeSize);
|
||||
}
|
||||
|
||||
- (void)testAutomaticallyManagesContentSizeWithSizeRangeSmallerThanParentSize
|
||||
{
|
||||
CGSize subnodeSize = CGSizeMake(100, 100);
|
||||
CGSize parentSize = CGSizeMake(100, 500);
|
||||
ASSizeRange sizeRange = ASSizeRangeMake(CGSizeMake(100, 100), CGSizeMake(100, 200));
|
||||
|
||||
self.subnode.style.preferredSize = subnodeSize;
|
||||
|
||||
[self.scrollNode layoutThatFits:sizeRange parentSize:parentSize];
|
||||
[self.scrollNode layout];
|
||||
|
||||
ASXCTAssertEqualSizes(self.scrollNode.calculatedSize, sizeRange.max);
|
||||
ASXCTAssertEqualSizes(self.scrollNode.view.contentSize, subnodeSize);
|
||||
}
|
||||
|
||||
- (void)testAutomaticallyManagesContentSizeWithSizeRangeBiggerThanParentSize
|
||||
{
|
||||
CGSize subnodeSize = CGSizeMake(100, 200);
|
||||
CGSize parentSize = CGSizeMake(100, 100);
|
||||
ASSizeRange sizeRange = ASSizeRangeMake(CGSizeMake(100, 150));
|
||||
|
||||
self.subnode.style.preferredSize = subnodeSize;
|
||||
|
||||
[self.scrollNode layoutThatFits:sizeRange parentSize:parentSize];
|
||||
[self.scrollNode layout];
|
||||
|
||||
ASXCTAssertEqualSizes(self.scrollNode.calculatedSize, sizeRange.min);
|
||||
ASXCTAssertEqualSizes(self.scrollNode.view.contentSize, subnodeSize);
|
||||
}
|
||||
|
||||
- (void)testAutomaticallyManagesContentSizeWithInvalidCalculatedSizeForLayout
|
||||
{
|
||||
CGSize subnodeSize = CGSizeMake(100, 200);
|
||||
CGSize parentSize = CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX);
|
||||
ASSizeRange sizeRange = ASSizeRangeUnconstrained;
|
||||
|
||||
self.subnode.style.preferredSize = subnodeSize;
|
||||
|
||||
[self.scrollNode layoutThatFits:sizeRange parentSize:parentSize];
|
||||
[self.scrollNode layout];
|
||||
|
||||
ASXCTAssertEqualSizes(self.scrollNode.calculatedSize, subnodeSize);
|
||||
ASXCTAssertEqualSizes(self.scrollNode.view.contentSize, subnodeSize);
|
||||
}
|
||||
|
||||
- (void)testASScrollNodeAccessibility {
|
||||
ASDisplayNode *scrollNode = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||
node.isAccessibilityContainer = YES;
|
||||
node.accessibilityLabel = @"node";
|
||||
[scrollNode addSubnode:node];
|
||||
node.frame = CGRectMake(0,0,100,100);
|
||||
ASTextNode2 *text = [[ASTextNode2 alloc] init];
|
||||
text.attributedText = [[NSAttributedString alloc] initWithString:@"text"];
|
||||
[node addSubnode:text];
|
||||
|
||||
ASTextNode2 *text2 = [[ASTextNode2 alloc] init];
|
||||
text2.attributedText = [[NSAttributedString alloc] initWithString:@"text2"];
|
||||
[node addSubnode:text2];
|
||||
__unused UIView *view = scrollNode.view;
|
||||
XCTAssertTrue(node.view.accessibilityElements.firstObject, @"node");
|
||||
|
||||
// Following tests will only pass when accessibility is enabled.
|
||||
// More details: https://github.com/TextureGroup/Texture/pull/1188
|
||||
|
||||
// A bunch of a11y containers each of which hold aggregated labels.
|
||||
/* NSArray *a11yElements = [scrollNode.view accessibilityElements];
|
||||
XCTAssertTrue(a11yElements.count > 0, @"accessibilityElements should exist");
|
||||
|
||||
UIAccessibilityElement *container = a11yElements.firstObject;
|
||||
XCTAssertTrue(container.isAccessibilityElement == false && container.accessibilityElements.count > 0);
|
||||
UIAccessibilityElement *ae = container.accessibilityElements.firstObject;
|
||||
XCTAssertTrue([[ae accessibilityLabel] isEqualToString:@"node, text, text2"]);
|
||||
*/
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
//
|
||||
// ASSnapshotTestCase.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdocumentation"
|
||||
#import <FBSnapshotTestCase/FBSnapshotTestCase.h>
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
#import "ASDisplayNodeTestsHelper.h"
|
||||
|
||||
@class ASDisplayNode;
|
||||
|
||||
NSOrderedSet *ASSnapshotTestCaseDefaultSuffixes(void);
|
||||
|
||||
#define ASSnapshotVerifyNode(node__, identifier__) \
|
||||
{ \
|
||||
[ASSnapshotTestCase hackilySynchronouslyRecursivelyRenderNode:node__]; \
|
||||
FBSnapshotVerifyLayerWithOptions(node__.layer, identifier__, ASSnapshotTestCaseDefaultSuffixes(), 0) \
|
||||
}
|
||||
|
||||
#define ASSnapshotVerifyLayer(layer__, identifier__) \
|
||||
FBSnapshotVerifyLayerWithOptions(layer__, identifier__, ASSnapshotTestCaseDefaultSuffixes(), 0);
|
||||
|
||||
#define ASSnapshotVerifyView(view__, identifier__) \
|
||||
FBSnapshotVerifyViewWithOptions(view__, identifier__, ASSnapshotTestCaseDefaultSuffixes(), 0);
|
||||
|
||||
#define ASSnapshotVerifyViewWithTolerance(view__, identifier__, tolerance__) \
|
||||
FBSnapshotVerifyViewWithOptions(view__, identifier__, ASSnapshotTestCaseDefaultSuffixes(), tolerance__);
|
||||
|
||||
@interface ASSnapshotTestCase : FBSnapshotTestCase
|
||||
|
||||
/**
|
||||
* Hack for testing. ASDisplayNode lacks an explicit -render method, so we manually hit its layout & display codepaths.
|
||||
*/
|
||||
+ (void)hackilySynchronouslyRecursivelyRenderNode:(ASDisplayNode *)node;
|
||||
|
||||
@end
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue